Compare commits

..

565 Commits

Author SHA1 Message Date
2dust 842c32f29f up 1.9.2 2024-09-24 09:41:16 +08:00
2dust 0501de1658 Bug fix 2024-09-23 09:59:25 +08:00
xubeiyan dbe26847c7 fix(MainRecyclerAdapter.kt): 将原有的主界面中服务器IP隐藏的逻辑中的1.23.45.67替换为1.23.45***更新为1.23.45.*** (#3585)
Co-authored-by: xubeiyan <yuyeyongdong@gmail.com>
2024-09-23 09:36:13 +08:00
2dust 806290f0a5 Remove no internet permission restrictions when apps are selected
https://github.com/2dust/v2rayNG/issues/3581
2024-09-23 09:33:21 +08:00
2dust 325c643314 Adjusting the text display 2024-09-22 17:05:17 +08:00
2dust 4adc0affbe Add HTTP protocol 2024-09-22 16:54:38 +08:00
solokot b5026095a0 Update Russian translation (#3583)
* Update Russian translation

* Update Russian translation
2024-09-22 16:51:17 +08:00
2dust fa7da3be10 Optimize Storage 2024-09-22 14:30:27 +08:00
2dust 35b44f1955 Add drag-and-drop sorting to grouping 2024-09-22 13:20:32 +08:00
2dust 4708ee8823 Add preset ruleset routing 2024-09-21 16:44:15 +08:00
2dust 52471f2ace Optimize UI 2024-09-21 16:02:25 +08:00
2dust 0b065c745d Refactor routing function 2024-09-21 15:36:45 +08:00
2dust 9ce8244065 Optimize UI 2024-09-20 15:06:27 +08:00
2dust b7fafa1bf9 Optimize MmkvManager 2024-09-20 09:33:07 +08:00
2dust 114c974ce5 Improvements MmkvManager 2024-09-19 19:43:13 +08:00
solokot e035925d25 Update Russian translation (#3570) 2024-09-19 15:04:38 +08:00
2dust c0fda6fcba up 1.9.1 2024-09-19 14:54:17 +08:00
2dust 75c90e3c45 Resolving pre-proxy domain issues 2024-09-19 13:45:28 +08:00
2dust 9960f49698 Adding pre-proxy to group 2024-09-19 09:11:42 +08:00
2dust 1a2c4cc9a1 Adding pre-proxy setting to group 2024-09-19 09:09:04 +08:00
2dust ee4f05b07d up 1.9.0 2024-09-17 13:47:21 +08:00
2dust 17ef476ede Bug fix 2024-09-17 13:39:02 +08:00
2dust 141b98631c Update libs.versions.toml 2024-09-17 11:08:49 +08:00
2dust 845562bca3 adapter noises 2024-09-17 11:02:13 +08:00
2dust 105a41eeea up 1.8.40 2024-09-08 08:44:21 +08:00
2dust 9a9d315e62 up 1.8.39 2024-08-30 19:12:51 +08:00
2dust c42aa93bf7 Add UDP noise
https://github.com/XTLS/Xray-core/commit/002d08bf834ed6abfb402a040300bd17f2f3b21b
2024-08-30 19:11:51 +08:00
Tamim Hossain a7664f03aa Update Kotlin Version In Readme.md (#3525)
Update Kotlin Version In Readme.md
2024-08-30 14:53:41 +08:00
Tamim Hossain fa341c9a5a Added wireguard regular config parsing feature, which we discussed on issue #3497 (#3521) 2024-08-29 19:31:44 +08:00
Tamim Hossain a15ab4759e Close the service tag correctly (#3519) 2024-08-29 19:29:40 +08:00
2dust b8939763d4 up 1.8.38 2024-08-17 19:46:20 +08:00
2dust 51adca8568 Format code 2024-08-17 19:40:04 +08:00
Tamim Hossain f646eff048 Removed internet check (#3494)
Removed the internet connection check before connecting as per the request in issue #3486
2024-08-17 14:59:21 +08:00
2dust fee0a016d8 Optimized search 2024-08-17 14:58:48 +08:00
2dust f040fa5c08 Bug fix
https://github.com/2dust/v2rayNG/issues/3488
2024-08-16 18:12:53 +08:00
2dust 7f3c6b4665 Bug fix 2024-08-16 17:59:36 +08:00
Tamim Hossain 8b806fe0be Fix logcat flush blocking call on the wrong dispatcher (#3491)
* Fix logcat flush blocking call on the wrong dispatcher

Moved the blocking `process.waitFor()` call to `Dispatchers.IO` to avoid potential thread starvation on `Dispatchers.Default`. This change ensures that the I/O-bound operation does not interfere with CPU-bound tasks, improving overall performance and responsiveness.

* Fix logcat flush blocking call on the wrong dispatcher

Moved the blocking `process.waitFor()` call to `Dispatchers.IO` to avoid potential thread starvation on `Dispatchers.Default`. This change ensures that the I/O-bound operation does not interfere with CPU-bound tasks, improving overall performance and responsiveness.
2024-08-16 17:50:04 +08:00
Tamim Hossain b37d8c2369 Refactor null handling with .orEmpty() for better readability (#3490)
Replaced null handling using `?: ""` with `.orEmpty()` for improved readability. The `.orEmpty()` extension function provides a more concise and expressive way to handle null strings by returning an empty string if the original string is null. This change enhances code clarity and consistency.
2024-08-16 17:49:29 +08:00
Tamim Hossain b5451e9d3d Update getSerializableExtra usage for Android 13 or later (#3489)
Adjusted the usage of `getSerializableExtra` to handle its deprecation in Android 13 (API level 33) and later. Implemented the method requiring class type specification for retrieving `Pair<String, Long>` in newer API levels, while maintaining compatibility with older versions using the legacy approach. This change ensures proper functionality across all supported Android versions.
2024-08-16 17:46:13 +08:00
Tamim Hossain b2235d4c38 Fix issue #3475 (#3485)
Fix issue where the label in the tile and widget was not displaying as `v2rayNG` for Russian language users
2024-08-16 10:09:53 +08:00
Tamim Hossain c8ba5d727e Updated WorkManager (#3483)
* Updated WorkManager

Updated the WorkManager library to latest version and also updated the code for its initialization.

* Updated WorkManager

Updated the WorkManager library to latest version and also updated the code for its initialization.
2024-08-15 17:07:25 +08:00
solokot a3de44cd0a Update Russian translation (#3482) 2024-08-15 17:06:11 +08:00
2dust 214d9e1c53 up 1.8.37 2024-08-15 09:58:44 +08:00
2dust 92900c3f74 Optimize text descriptions 2024-08-15 09:54:42 +08:00
2dust e17e566daa Update subscriptions based on grouping
https://github.com/2dust/v2rayNG/issues/3445
2024-08-14 20:16:26 +08:00
2dust df4e232087 Code Optimization 2024-08-14 19:58:32 +08:00
2dust 828a39331b Add a progress bar 2024-08-14 17:55:41 +08:00
2dust a0223a3eee logic optimization
https://github.com/2dust/v2rayNG/issues/3470
2024-08-14 15:06:21 +08:00
2dust 7f6a526b25 Add profile filtering
https://github.com/2dust/v2rayNG/issues/3471
2024-08-14 10:46:42 +08:00
2dust 9983ea25d2 Performance optimization 2024-08-13 21:17:27 +08:00
2dust 47166b937f Main interface list performance optimization 2024-08-13 17:07:07 +08:00
2dust 0a09966e81 Update UserAssetActivity.kt 2024-08-13 16:09:53 +08:00
2dust 7ec34e934e Bug fix 2024-08-13 15:11:53 +08:00
2dust f2b03e7492 Merge branch 'master' of https://github.com/2dust/v2rayNG 2024-08-13 15:10:34 +08:00
2dust 5208bd62c5 Optimized UI
com.google.android.material.progressindicator.LinearProgressIndicator
2024-08-13 14:39:45 +08:00
AmirMohammad Yazdanmanesh d9f0854c27 Fix some Persian word translations (#3469)
* Translate fingerprint

* The word 'موفقیت' is a noun and does not mean 'success' in this context

* Translate 'about'
2024-08-12 19:35:50 +08:00
Tamim Hossain 6be125b5cb Replace deprecated packagingOptions with packaging for jniLibs configuration (#3464)
Replaced deprecated packagingOptions with packaging for jniLibs configuration.

Updated build.gradle file to use the new packaging configuration method as the previous
packagingOptions method is deprecated. This change ensures compatibility with the latest
Gradle plugin versions and avoids any potential issues with outdated configuration.

Changes:
- Updated from `packagingOptions { jniLibs { useLegacyPackaging = true } }`
  to `packaging { jniLibs { useLegacyPackaging = true } }`
2024-08-12 09:20:23 +08:00
2dust c884c098fd Bug fix
https://github.com/2dust/v2rayNG/issues/3426
2024-08-10 20:11:34 +08:00
AnGgIt86 f77fe05c92 Update RxPermissions and rxjava3 (#3463) 2024-08-10 19:56:37 +08:00
Tamim Hossain f3bfa8ceba Update null safety handling with orEmpty() (#3461)
Replaced ?: "" with orEmpty() for improved null safety

Updated all instances of the null-coalescing operator (?: "") with orEmpty() to enhance null safety across the codebase. This change ensures that an empty string is used when a nullable value is null, improving consistency and reducing potential null-related issues.
2024-08-10 19:54:45 +08:00
Tamim Hossain ae7d9d87d2 Inline return statements for cleaner code (#3460)
Refactored conditional return statements to be inline.
- Simplified code readability by inlining return statements.
- Maintained functionality while improving code clarity.
2024-08-10 19:53:35 +08:00
Tamim Hossain 1652040c1c Simplify search logic using SearchView (#3459)
Refactor the search functionality to use SearchView for better handling of search queries.
- Removed redundant code and replaced it with SearchView's onQueryTextChange listener.
- Improved readability and maintainability of the search logic.
2024-08-10 19:51:45 +08:00
Tamim Hossain 818b7cdff4 Replace global scope with CoroutineScope for socket operations (#3454)
Replaced the use of global scope with CoroutineScope(Dispatchers.IO) in the socket operation function. This change improves coroutine management and ensures proper use of dispatchers for background tasks. The code now properly handles retries with exponential backoff and logs attempts and errors.
2024-08-10 19:48:53 +08:00
Tamim Hossain 48ce359d2d Fix: Refactor to use lazy initialization for binding (#3453)
Refactor binding initialization to use lazy delegation

Replaced direct initialization of binding with lazy initialization to improve performance and resource management. This change defers the creation of the binding object until it is actually needed, which can reduce memory usage and improve efficiency.

- Updated binding initialization to use lazy
- Ensured proper access and lifecycle handling

This update ensures that the binding is initialized only when required, leading to a more efficient use of resources.
2024-08-10 19:41:37 +08:00
Tamim Hossain e115bf0c6d Refactor EConfigType to use constants from AppConfig (#3451)
Updated the EConfigType enum to use protocol scheme constants from AppConfig. This change improves maintainability by centralizing protocol scheme definitions in a single location.
2024-08-10 19:34:24 +08:00
Tamim Hossain 0415b60ba5 Fix ABI split configuration for updated Gradle plugin API (#3449)
* Fix ABI split configuration for updated Gradle plugin API

Updated the ABI split configuration in the build.gradle file to use the new syntax.
Replaced deprecated `reset()` method with the updated `splits.abi` configuration block and added `universalApk = true` to include a universal APK.

* Unresolved reference: universalApk

Unresolved reference: universalApk
2024-08-10 19:32:32 +08:00
AmirMohammad Yazdanmanesh 4570fdb05f Implement a check to start V2Ray if the network connected (#3439)
* Implement isNetworkConnected functionality

* Implement a check to start V2Ray if the network connected
2024-08-10 19:23:52 +08:00
mayampi01 9d109e7ca9 Hardcode popular Android Private DNS rule to fix localhost DNS problem (#3433)
* hardcode popular Android Private DNS rule to fix localhost DNS problem

* V2rayConfigUtil.kt: Fix hosts type

* hardcode popular Android Private DNS: Add dns.pub
2024-08-10 19:21:47 +08:00
2dust 2574553180 up 1.8.36 2024-08-05 20:28:36 +08:00
2dust 66e77d50bd Optimized UI 2024-08-04 19:18:16 +08:00
mayampi01 253bd793d7 Add geosite:private and make it work (#3418)
* Add geosite:private and make it work

* Update AppConfig.kt

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2024-08-04 10:15:55 +08:00
2dust be30de6728 Update build.yml 2024-08-03 10:44:12 +08:00
Tamim Hossain a1455bbb1c Updated getLocale function to use appropriate Locale constants (#3413)
Updated getLocale function to use appropriate Locale constants
2024-08-03 10:38:00 +08:00
Tamim Hossain 1ac19ae3e9 Fix getDarkModeStatus to handle configuration changes more accurately (#3412)
Updated getDarkModeStatus function to improve detection of dark mode status.
2024-08-03 10:36:21 +08:00
Tamim Hossain 6e6ca209df Refactor base64 decode function to simplify handling (#3411)
Refactored the decode function to simplify base64 decoding and improve handling of various base64 formats. Removed redundant code and improved clarity.
2024-08-03 10:35:15 +08:00
Tamim Hossain 52699967cd Simplify parseInt function (#3410)
Refactored the parseInt function to use toIntOrNull() for more concise and idiomatic error handling. This change simplifies the code and avoids the need for manual exception handling
2024-08-03 10:33:28 +08:00
Tamim Hossain 146d20ce86 Optimize strState Handling by Removing Redundant try-catch (#3409)
Refactored the code for handling the `strState` string to remove the redundant `try-catch` block. Updated to use safe call with the Elvis operator for improved readability and efficiency. This change simplifies the code and reduces the need for exception handling in this context.
2024-08-03 10:30:38 +08:00
Tamim Hossain b6959b5990 Refactor filterConfig Function for Simplified Code and Reusability (#3408)
Refactored the `filterConfig` function in `MainViewModel` to simplify the code and improve reusability. The updated implementation reduces redundancy, enhances readability, and streamlines the configuration filtering process, making the code more efficient and maintainable.
2024-08-03 10:20:28 +08:00
Tamim Hossain 06649df8b1 Add Documentation Comments to AppConfig Constants (#3406)
This branch adds comprehensive documentation comments to the AppConfig object in the codebase
2024-08-03 09:52:00 +08:00
Tamim Hossain 0174ed9082 Remove redundant access modifier (#3405)
Removed unnecessary access modifier
2024-08-03 09:49:44 +08:00
2dust adabb281b1 Update string 2024-08-02 10:11:49 +08:00
2dust 63e710d1ab Simplifying the filter configuration file 2024-08-02 10:05:40 +08:00
Tamim Hossain 509a568446 Added Bangla Translation (#3399)
Added Bangla translations for all resources in the project. The translations were provided by a native Bangla speaker from Bangladesh—myself! This update includes translations for all project elements to support localization for Bangla-speaking users.
2024-08-01 20:18:51 +08:00
Tamim Hossain 6fd94b53f0 Refactor charset usage and consolidate function implementation (#3398)
- Updated charset usage from string literal "UTF_8" to standard `Charsets.UTF_8`.
- Consolidated the implementation of the `getDelayTestUrl()` function.
- Improved code readability and consistency with these changes.
2024-08-01 20:17:02 +08:00
Tamim Hossain 164412fa34 Refactor GlobalScope Usage to Prevent Memory Leaks (#3396)
Replaced usage of GlobalScope with a specific coroutine scope tied to the lifecycle of the service . This change helps to prevent potential memory leaks and unintended behavior by ensuring coroutines are properly managed and tied to the appropriate lifecycle.
2024-08-01 20:07:30 +08:00
Tamim Hossain 514ca0810e Refactor V2RayServiceManager to Use Constants for Configuration Values (#3395)
Refactor V2RayServiceManager to Use Constants for Configuration Values.
- Replaced hardcoded string literals for 'uplink' and 'downlink' with constants from AppConfig.
- Introduced constants for notification channel ID and name.
2024-08-01 20:05:09 +08:00
Tamim Hossain 7582f86482 Refactor extension functions for better performance and clarity (#3394)
Refactor Kotlin extension functions for clarity and performance

- Updated `v2RayApplication` extension to handle potential ClassCastException.
- Simplified `toast` functions for better readability.
- Refactored `toTrafficString` function to reduce redundancy and improve performance.
- Minor improvements to JSON and URL connection handling.
2024-08-01 19:44:07 +08:00
mayampi01 4b4c46e5ae Drop geolocation-cn (cn contains geolocation-cn) (#3393) 2024-08-01 19:38:08 +08:00
2dust 162e156b33 Bug fix
https://github.com/2dust/v2rayNG/pull/3390
2024-08-01 09:47:33 +08:00
mayampi01 bc7d1971ef Add localhost DNS support (#3384) 2024-08-01 09:27:46 +08:00
2dust bbdee92f37 Determine custom rule order based on preset rules
https://github.com/2dust/v2rayNG/pull/3388
2024-07-31 21:01:16 +08:00
Tamim Hossain cf9e830cc7 Replace deprecated onBackPressed with onBackPressedDispatcher (#3389)
Replaced deprecated `onBackPressed` with `onBackPressedDispatcher`. The `onBackPressedDispatcher` handles the home button press by delegating to the appropriate back navigation method.
2024-07-31 20:36:52 +08:00
mayampi01 804e425a87 Make IPIfNonMatch actually work (#3387) 2024-07-31 20:18:40 +08:00
mayampi01 cc21383928 Fix typo: s/Domian/Domain/g (#3383) 2024-07-31 20:15:06 +08:00
2dust 413f4efd69 up 1.8.35 2024-07-29 19:12:58 +08:00
Tamim Hossain de69605eff Introduced version catalog for streamlined dependency management (#3377)
This pull request integrates a version catalog (libs.versions.toml) to centralize dependency management within the project. By utilizing this approach, we enhance dependency consistency, reduce maintenance overhead, and facilitate version updates across multiple modules. This change provides a more organized and efficient way to manage project dependencies.
2024-07-28 09:27:58 +08:00
mayampi01 9338ba3525 Set UseIP in Direct to break the Android Private DNS dead loop (#3362)
* Set UseIP in Direct to break the Android Private DNS dead loop

* fakedns: No need to set UseIP again
2024-07-25 19:50:23 +08:00
2dust 322b6ec615 Bug fix 2024-07-25 19:46:39 +08:00
2dust 304232d029 up 1.8.34 2024-07-22 11:34:44 +08:00
2dust f80c3bfe07 up 1.8.33 2024-07-20 15:05:45 +08:00
2dust bb0a62fc8b up 1.8.32 2024-07-18 10:45:05 +08:00
2dust 5bfdca6cd9 Bug fix 2024-07-18 10:15:58 +08:00
2dust 447e712a9d up 1.8.31 2024-07-16 14:05:34 +08:00
2dust 8bb03f189d up 1.8.30 2024-07-13 10:43:46 +08:00
2dust 3b0554cd9b up 1.8.29 2024-07-12 13:30:38 +08:00
2dust 858101b0d9 Bug fix
https://github.com/2dust/v2rayNG/issues/3234
https://github.com/2dust/v2rayNG/issues/3291
2024-07-11 16:41:38 +08:00
2dust a7cf8bee28 Code clean
Add allowInsecure to Share
2024-07-11 16:40:25 +08:00
2dust af1ec7bea9 Bug fix 2024-07-09 12:29:04 +08:00
2dust 002bf7ef22 up 1.8.28 2024-07-07 18:14:36 +08:00
2dust 6919e2336d Bug fix
https://github.com/2dust/v2rayNG/issues/3278
2024-07-01 18:09:57 +08:00
2dust 5a5bd22073 Bug fix
https://github.com/2dust/v2rayNG/issues/3232
2024-07-01 10:34:45 +08:00
2dust a726f00f35 Bug fix 2024-06-29 16:47:06 +08:00
2dust 79297f8a42 up 1.8.27
App bundle support
2024-06-29 15:32:01 +08:00
2dust e3f70ac253 Add LEANBACK_LAUNCHER 2024-06-29 15:29:20 +08:00
GFW-knocker 838b346fcc fix wireguard issue (#3260)
fix crash when invalid wireguard config imported
2024-06-28 09:41:55 +08:00
solokot eba9545ccf Update Russian translation to 1.8.26 (#3238) 2024-06-24 09:09:35 +08:00
2dust 890ade9495 up 1.8.26 2024-06-21 20:33:34 +08:00
2dust 518ef1e0ec Add splithttp transport for xray 2024-06-21 20:18:04 +08:00
2dust bdcecfca72 Hardcode gstatic.com taking detour [proxy] 2024-06-21 16:48:30 +08:00
2dust 1363846ac4 Fix view 2024-06-08 20:24:23 +08:00
2dust 30347546a2 Optimized code 2024-06-08 10:17:54 +08:00
2dust ac7eb28e91 Auto update subscriptions when adding a url 2024-06-07 21:06:27 +08:00
2dust 748405473b Fix
https://github.com/2dust/v2rayNG/issues/3175
2024-06-07 10:51:13 +08:00
2dust 1080390bed Adding a second test when a delayed test fails
https://www.google.com/generate_204
2024-06-07 10:25:23 +08:00
2dust c48725c7dd Bug fix
https://github.com/2dust/v2rayNG/issues/3193
2024-06-06 19:48:50 +08:00
2dust a5287dbadc Optimized code 2024-06-02 20:50:27 +08:00
solokot ee5a3b0dd9 Update Russian translation (#3172) 2024-05-30 20:06:16 +08:00
2dust 3d001541e5 Refactor the parser 2024-05-30 20:04:43 +08:00
2dust b376b229b9 Add progress bar when subscription is updated 2024-05-30 16:07:11 +08:00
2dust 33b6203978 Bug fix 2024-05-29 20:30:10 +08:00
2dust 2d803e009c Bug fix
https://github.com/2dust/v2rayNG/issues/3168
2024-05-29 17:41:58 +08:00
2dust 2ddbe38781 Merge branch 'master' of https://github.com/2dust/v2rayNG 2024-05-25 20:32:37 +08:00
user09283 96181a2b8d Update strings.xml (#3152)
Update Vietnamese language!
2024-05-24 20:42:30 +08:00
2dust 8308b8eaf2 Bug fix
android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{8f445a6 u0 com.v2ray.ang/.service.V2RayVpnService}
2024-05-24 10:35:46 +08:00
2dust 00f26ff529 up 1.8.25 2024-05-23 10:54:47 +08:00
2dust 49dcdf3ae5 used resources 2024-05-22 17:41:55 +08:00
2dust 409b431d1c unused resources 2024-05-20 17:12:54 +08:00
2dust 6da988e3db up 1.8.24 2024-05-18 13:45:44 +08:00
2dust fd8f8306ee up dependency 2024-05-18 13:44:43 +08:00
2dust 74b342f5c6 Bug fix
https://github.com/2dust/v2rayNG/issues/3126
2024-05-17 14:04:15 +08:00
2dust 2504ec79ee Change delayed test URL
https://github.com/2dust/v2rayNG/issues/3110
2024-05-14 10:53:15 +08:00
2dust 3e7b211b17 Bug fix
https://github.com/2dust/v2rayNG/issues/3106
2024-05-12 16:47:27 +08:00
solokot 13d5514a4c Update Russian translation (#3098) 2024-05-12 11:55:57 +08:00
2dust f0f9da0f1b Add delayed test URL 2024-05-12 11:55:10 +08:00
2dust f6d2c5f473 up 1.8.23 2024-05-08 17:18:17 +08:00
2dust 6e8dd5b250 Update strings.xml 2024-05-08 15:34:14 +08:00
2dust f4779bc50c Update strings.xml 2024-05-08 15:33:00 +08:00
ibrahem Qasim 432baf262d Update Arabic translation (#3089)
* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml
2024-05-07 09:42:15 +08:00
2dust a1d68fcde3 Added attempt to update subscription via proxy
https://github.com/2dust/v2rayNG/issues/2760
2024-05-05 17:08:05 +08:00
2dust e053db3dff Add routeOnly 2024-05-02 10:51:54 +08:00
solokot f624bd651e Update Russian translation (#3070) 2024-05-01 19:37:03 +08:00
2dust 84964c7f91 Add attributes to grpc
https://github.com/2dust/v2rayNG/issues/3041
2024-05-01 14:01:34 +08:00
2dust 96f56b468e Adjust DNS hardcoded geoip:cn issue
https://github.com/2dust/v2rayNG/issues/3061
2024-05-01 14:00:35 +08:00
2dust bdc3212f38 Rule start with ext and contains geoip added to the ips
https://github.com/2dust/v2rayNG/issues/3060
2024-05-01 10:43:47 +08:00
2dust 508ddf6df2 Share configuration 2024-04-28 19:47:54 +08:00
2dust 17af24d179 up 1.8.22 2024-04-26 13:45:59 +08:00
2dust c260e447ea Adjust UI 2024-04-25 17:52:34 +08:00
2dust 0eb40ae993 Adjust UI 2024-04-25 10:34:15 +08:00
ibrahem Qasim 18f3e39346 Update Arabic translation (#3042)
* Create android.yml

* Create gradle-publish.yml

* Update strings.xml

* Delete .github/workflows/android.yml

* Delete .github/workflows/gradle-publish.yml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml
2024-04-25 08:17:58 +08:00
2dust 311af02726 up 1.8.21 2024-04-23 17:32:20 +08:00
2dust b799969de8 Adjust UI 2024-04-23 13:45:50 +08:00
2dust 93541883bb Adjust UI 2024-04-22 10:35:16 +08:00
2dust 33430bff8d Adjust UI 2024-04-21 16:31:34 +08:00
2dust 3bc2081540 Adjust UI 2024-04-21 12:05:42 +08:00
2dust a3b1feabff Bug fix
https://github.com/2dust/v2rayNG/issues/3024
2024-04-20 15:38:55 +08:00
2dust 7af8d3843e Merge pull request #3025 from solokot/master
Update Russian translation
2024-04-20 15:11:02 +08:00
solokot 2235805800 Update Russian translation 2024-04-19 18:40:09 +03:00
2dust 364b521ec3 up 1.8.20 2024-04-19 21:07:38 +08:00
2dust 3cdaf4a8ee Bug fix
https://github.com/2dust/v2rayNG/issues/3010
2024-04-19 14:36:48 +08:00
2dust b2fc6dcdd0 Add switching theme dark or light 2024-04-19 13:41:52 +08:00
2dust 70e9320463 Merge pull request #3020 from FranzKafkaYu/optimize-ui-logic
style:optimize UI logic
2024-04-18 10:42:51 +08:00
FranzKafkayu 304c7ed068 style:optimize UI logic
1.add button for cancelling delete server config
2.toast when delete current using config
2024-04-17 18:37:17 +08:00
2dust ddb908f937 Bug fix 2024-04-17 16:39:53 +08:00
2dust accd17afd4 Bug fix 2024-04-17 15:49:07 +08:00
2dust 9f598b77b4 Merge pull request #3016 from solokot/master
Update Russian translation
2024-04-17 14:11:28 +08:00
solokot d82fa974b1 Update Russian translation 2024-04-16 17:13:22 +03:00
2dust fcbd4a0d48 Optimized storage of settings for SharedPreference 2024-04-16 20:23:58 +08:00
2dust 5cd2b8845e Adjust about style 2024-04-16 10:39:56 +08:00
2dust 723ab70170 Remove function migrateLegacy() 2024-04-16 10:29:07 +08:00
2dust a26bf3eeda Add backup and restore configuration functionality 2024-04-15 17:30:00 +08:00
2dust c33b6463c6 Merge pull request #3005 from solokot/master
Update Russian translation
2024-04-13 20:23:06 +08:00
solokot df995a3ab2 Update Russian translation 2024-04-13 10:39:33 +03:00
2dust 817f844212 Add about activity 2024-04-13 13:12:35 +08:00
2dust fa8113b8d7 Merge pull request #3002 from NetworkKeeper/patch-1
Ability to override built in `geosite.dat` and `geoip.dat`
2024-04-12 07:47:44 +08:00
2dust 99b95d8369 Merge pull request #3001 from NetworkKeeper/patch-2
Retry downloading geo assets without proxy
2024-04-12 07:47:19 +08:00
2dust fc2e4ff210 Optimize routing 2024-04-12 07:45:26 +08:00
NetworkKeeper 1063bf71d6 Retry downloading geo assets without proxy 2024-04-11 19:37:20 +03:00
NetworkKeeper f2af5c45e9 Ability to override built in geosite.dat and geoip.dat 2024-04-11 19:30:32 +03:00
2dust b132b0d2f0 Remove the type of routing rule 2024-04-11 11:00:46 +08:00
2dust 7869f99fc8 Adjust routing default rules 2024-04-11 09:21:57 +08:00
2dust 122f2eb400 Merge pull request #2995 from oXIIIo/master
fix(deps): Update Go version to 1.22.2 for Xray compatibility (1.8.10)
2024-04-11 07:47:36 +08:00
XIII 3c0f6eeb21 fix(deps): Update Go version to 1.22.2 for Xray compatibility (1.8.10) 2024-04-10 17:36:39 +03:30
2dust 19a109355b Merge pull request #2986 from kimsuelim/improve_build
Remove -XX:MaxPermSize option
2024-04-08 17:12:15 +08:00
Surim Kim 703965a0dd Remove -XX:MaxPermSize option.
Android Gradle requires JDK 17, and this option was removed between Java 11 and Java 17.
2024-04-08 10:05:28 +09:00
2dust 66f92c6c60 Bug fix
https://github.com/2dust/v2rayNG/issues/2972
2024-04-03 10:19:03 +08:00
2dust a1cdf6b7a5 Merge pull request #2966 from Malus-risus/master
Update dependency
2024-04-03 10:02:45 +08:00
Το μοχθηρό ^_^ 554c7b5687 Update build.gradle.kts 2024-04-02 09:34:43 +08:00
Το μοχθηρό ^_^ 2d987313a7 Delete renovate.json 2024-04-01 14:02:42 +08:00
Το μοχθηρό ^_^ 9eebe32bdf Merge pull request #31 from Malus-risus/renovate/com.android.library-8.x
Update plugin com.android.library to v8.3.1
2024-04-01 13:24:07 +08:00
renovate[bot] 8e9da0ad6f Update plugin com.android.library to v8.3.1 2024-04-01 05:15:22 +00:00
Το μοχθηρό ^_^ 2f20dea611 Merge pull request #30 from Malus-risus/renovate/com.android.application-8.x
Update plugin com.android.application to v8.3.1
2024-04-01 13:14:51 +08:00
renovate[bot] 167baf64a9 Update plugin com.android.application to v8.3.1 2024-04-01 04:47:28 +00:00
Το μοχθηρό ^_^ bd7a214f7f Merge pull request #29 from Malus-risus/renovate/gradle-8.x
Update dependency gradle to v8.7
2024-04-01 12:17:52 +08:00
Το μοχθηρό ^_^ f16d2d9a74 Merge pull request #28 from Malus-risus/renovate/com.tencent-mmkv-static-1.x
Update dependency com.tencent:mmkv-static to v1.3.4
2024-04-01 12:17:42 +08:00
renovate[bot] e984d2c274 Update dependency gradle to v8.7 2024-04-01 04:09:58 +00:00
renovate[bot] 8720d087ea Update dependency com.tencent:mmkv-static to v1.3.4 2024-04-01 04:09:24 +00:00
Το μοχθηρό ^_^ a1e19b9fcd Create renovate.json 2024-04-01 12:08:53 +08:00
2dust 6b9728dc84 Update 1.8.19 2024-03-30 17:25:08 +08:00
2dust 1ce9b7c0c8 Merge pull request #2956 from xfree-man/master
Add observatory to V2rayConfig, to enable load balancing features in a custom config
2024-03-27 07:53:57 +08:00
xfree-man ff75d3fdc2 Add observatory to V2rayConfig 2024-03-26 13:31:04 +04:00
2dust 8154812570 Merge pull request #2948 from Amir-yazdanmanesh/master
Fix deprecated codes
2024-03-22 09:58:44 +08:00
amir 1dcd2478fc Expression should use clarifying parentheses 2024-03-20 12:37:53 +03:30
amir 0515806e92 Remove deprecated codes in MainActivity.kt 2024-03-20 12:37:32 +03:30
2dust f286506ba4 Bug fix 2024-03-16 17:11:38 +08:00
2dust 48be736275 Merge pull request #2933 from Utaea/pr
Add support to transport for shadowsocks
2024-03-16 16:58:20 +08:00
2dust af0faeab16 Merge pull request #2928 from yuhan6665/authority
Add authority for gRPC
2024-03-16 16:31:12 +08:00
2dust fb6edee842 Merge branch 'master' into authority 2024-03-16 16:30:28 +08:00
2dust 1209c4b92a Merge pull request #2921 from solokot/master
Update & improve Russian translation
2024-03-16 16:26:31 +08:00
Utaea e4e668b492 Add support to transport for shadowsocks 2024-03-15 03:51:49 +00:00
yuhan6665 92d3136a23 Add authority for gRPC 2024-03-13 12:57:08 -04:00
solokot f6e74ddb45 Update & improve Russian translation 2024-03-12 10:22:16 +03:00
2dust c34f332771 Update 1.8.18 2024-03-12 10:49:52 +08:00
2dust eb1d71bda6 Add httpupgrade 2024-03-12 10:49:32 +08:00
2dust 3cdd5822cb Merge pull request #2915 from Malus-risus/master
Update dependency
2024-03-10 10:25:07 +08:00
Το μοχθηρό ^_^ 8f924b5ce1 Delete renovate.json 2024-03-09 17:33:08 +08:00
Το μοχθηρό ^_^ 2ae645dce5 Merge pull request #24 from Malus-risus/renovate/com.android.library-8.x
Update plugin com.android.library to v8.3.0
2024-03-09 17:20:13 +08:00
renovate[bot] f848a7119f Update plugin com.android.library to v8.3.0 2024-03-09 09:12:10 +00:00
Το μοχθηρό ^_^ 2fa41db32f Merge pull request #23 from Malus-risus/renovate/com.android.application-8.x
Update plugin com.android.application to v8.3.0
2024-03-09 17:11:24 +08:00
Το μοχθηρό ^_^ 9af4516761 Merge pull request #22 from Malus-risus/renovate/kotlin-ksp
Update Kotlin, KSP to v1.9.23
2024-03-09 17:11:03 +08:00
renovate[bot] 189b07124d Update plugin com.android.application to v8.3.0 2024-03-09 08:19:13 +00:00
renovate[bot] e6e5cdcdcd Update Kotlin, KSP to v1.9.23 2024-03-09 08:19:07 +00:00
Το μοχθηρό ^_^ a31e4dab85 Create renovate.json 2024-03-09 16:18:14 +08:00
2dust b2a9397dc7 Revert "Merge pull request #2903 from ssszmath/master"
This reverts commit a536df1a02, reversing
changes made to 2dc82b7e83.
2024-03-07 08:22:28 +08:00
2dust a536df1a02 Merge pull request #2903 from ssszmath/master
Fixing the problem of connecting to the Internet after publishing the application on Google Play in aab format
2024-03-07 08:06:04 +08:00
Sajad Sajad 1a2751563d add NativeLibs configuration to manifest and gradle.properties 2024-03-05 19:12:04 +03:30
2dust 2dc82b7e83 Merge pull request #2892 from vfarid/add_remark_to_custom_configs
Added remarks to all custom configs
2024-03-03 16:59:03 +08:00
2dust b02d81ae53 Merge pull request #2889 from vfarid/more_fragment_options
More fragment options + Fix for reality configs
2024-03-03 16:53:22 +08:00
Vahid Farid 773bcc5658 Set V2rayConfig remarks based on ServerConfig in V2rayConfigUtil.kt 2024-03-02 18:30:05 +03:30
Vahid Farid 9142b9cfb4 Update AngConfigManager.kt
Added remark for single config subscription
Removed unnecessary code in multiple config subscription update
2024-03-02 18:27:59 +03:30
Vahid Farid 376882d975 Add remark to custom-config for export in ServerCustomConfigActivity.kt 2024-03-02 18:26:00 +03:30
Vahid Farid f330c32ccc Get config name from custom-config's remark during import in MainViewModel.kt 2024-03-02 18:25:01 +03:30
Vahid Farid 58dbb71b53 Fix fragment for reality configs 2024-03-02 16:54:17 +03:30
Vahid Farid ebd0257f6e Added new fragment packets for reality 2024-03-02 16:53:34 +03:30
2dust 945c584fc6 Merge pull request #2886 from MrMalekfar/master
Addressing issue #2880
2024-03-01 08:07:55 +08:00
MrMalekfar 19cd24c37a Update AngConfigManager.kt 2024-03-01 02:35:56 +03:30
MrMalekfar 3994810c4e Update AngConfigManager.kt 2024-03-01 02:06:36 +03:30
MrMalekfar d86e68c77a Update AngConfigManager.kt 2024-03-01 01:51:08 +03:30
MrMalekfar cc7bdefe54 Update AngConfigManager.kt 2024-03-01 01:27:59 +03:30
MrMalekfar 51b32a030a Update AngConfigManager.kt 2024-02-29 14:15:05 +03:30
MrMalekfar 6c76ddd145 Update AngConfigManager.kt 2024-02-29 14:12:19 +03:30
MrMalekfar 0d12cc5dc8 Update AngConfigManager.kt 2024-02-29 13:55:52 +03:30
MrMalekfar cfb756723c Update AngConfigManager.kt 2024-02-29 11:07:39 +03:30
MrMalekfar 0e9f198341 Update AngConfigManager.kt 2024-02-29 09:59:58 +03:30
MrMalekfar 1fa1325630 Update AngConfigManager.kt 2024-02-29 09:50:31 +03:30
MrMalekfar dc79d3a897 Update AngConfigManager.kt 2024-02-29 09:25:06 +03:30
MrMalekfar 486f3ffc96 Update AngConfigManager.kt 2024-02-29 09:23:46 +03:30
2dust bf030e12f5 Merge pull request #2883 from admarty/master
Update vi translation
2024-02-29 08:58:12 +08:00
MrMalekfar e80cce9696 Update AngConfigManager.kt 2024-02-29 02:04:42 +03:30
admarty 5bb9ecce47 Update vi translation 2024-02-28 22:32:26 +07:00
MrMalekfar 5eb09aa54e Update AngConfigManager.kt 2024-02-28 16:31:30 +03:30
MrMalekfar ff261d9939 Update AngConfigManager.kt 2024-02-28 15:22:57 +03:30
MrMalekfar 1624ec87b2 Update AngConfigManager.kt 2024-02-28 15:06:37 +03:30
MrMalekfar f1062f2f45 Update AngConfigManager.kt 2024-02-28 14:51:54 +03:30
2dust 39341c27bc up 1.8.17 2024-02-26 08:54:51 +08:00
2dust 31a90cec2b Merge pull request #2869 from Malus-risus/master
Refactor: Simplify Extensions and Improve Compatibility
2024-02-26 08:11:39 +08:00
Το μοχθηρό ^_^ 617fc63393 Delete renovate.json 2024-02-25 19:59:53 +08:00
Το μοχθηρό ^_^ 802f2cf3eb Update _Ext.kt 2024-02-25 19:05:48 +08:00
Το μοχθηρό ^_^ c7efcde868 Update _Ext.kt 2024-02-25 18:42:39 +08:00
Το μοχθηρό ^_^ e858179204 Update _Ext.kt 2024-02-25 18:31:42 +08:00
Το μοχθηρό ^_^ 6871b0b950 Merge branch '2dust:master' into master 2024-02-25 10:42:48 +08:00
2dust e9a27a1585 Merge pull request #2868 from NagisaEfi/master
Update translation
2024-02-25 09:28:36 +08:00
Το μοχθηρό ^_^ 976b765629 Create renovate.json 2024-02-24 01:09:56 +08:00
NagisaEfi 126b9b6516 Update translation 2024-02-23 23:23:39 +08:00
2dust 94dab02b54 Merge pull request #2856 from Malus-risus/master
Remove unnecessary parentheses
2024-02-20 13:02:35 +08:00
Το μοχθηρό ^_^ a893b87730 Update MainRecyclerAdapter.kt 2024-02-19 14:36:28 +08:00
Το μοχθηρό ^_^ 7db2ddd1f7 Update V2RayServiceManager.kt 2024-02-19 14:34:50 +08:00
Το μοχθηρό ^_^ 748980aa1a Update PerAppProxyActivity.kt 2024-02-19 14:33:12 +08:00
Το μοχθηρό ^_^ 96d416066e Update AppManagerUtil.kt 2024-02-19 14:31:47 +08:00
Το μοχθηρό ^_^ 3955bb16bc Update _Ext.kt 2024-02-19 14:29:35 +08:00
Το μοχθηρό ^_^ de9bbf842f Update Utils.kt 2024-02-19 12:42:56 +08:00
Το μοχθηρό ^_^ 336b673746 Update AngConfigManager.kt 2024-02-19 12:38:24 +08:00
Το μοχθηρό ^_^ f1b6b1e871 Update SettingsActivity.kt 2024-02-19 12:07:13 +08:00
2dust fba4c03bb5 Bug fix 2024-02-17 09:55:26 +08:00
2dust a32ae5b53f Merge pull request #2848 from NotDubious/master
Update Farsi translation
2024-02-17 09:23:11 +08:00
Mahyar 589e0f38fd Update Farsi translation 2024-02-16 11:07:31 +03:30
2dust e21116680e Up 1.8.16 2024-02-16 13:48:41 +08:00
2dust e1960f5aff Write remarks when pasting subscription link
https://github.com/2dust/v2rayNG/issues/2845
2024-02-16 09:52:10 +08:00
2dust 327ba57088 Improve 2024-02-16 09:39:39 +08:00
2dust 04e1b024e2 Merge pull request #2846 from Malus-risus/master
Update dependency
2024-02-16 09:20:25 +08:00
2dust b59fe9b57b Delete renovate.json 2024-02-16 09:19:29 +08:00
2dust fab0b756de Merge pull request #2844 from solokot/master
Update Russian translation
2024-02-16 09:12:59 +08:00
Το μοχθηρό ^_^ aa2727d0d0 Merge pull request #10 from Malus-risus/renovate/org.jetbrains.kotlinx-kotlinx-coroutines-android-1.x
Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.8.0
2024-02-16 00:58:49 +08:00
Το μοχθηρό ^_^ ce3dd73a81 Merge branch '2dust:master' into master 2024-02-16 00:54:06 +08:00
renovate[bot] 2eab209fea Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-android to v1.8.0 2024-02-15 16:52:16 +00:00
Το μοχθηρό ^_^ 02476657bf Merge pull request #11 from Malus-risus/renovate/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.x
Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-core to v1.8.0
2024-02-16 00:51:46 +08:00
renovate[bot] 35602120e8 Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-core to v1.8.0 2024-02-15 15:36:59 +00:00
solokot 9e800e08ab Update Russian translation 2024-02-15 08:25:30 +03:00
2dust 09fc20794e Improve and translate 2024-02-14 19:40:51 +08:00
2dust 3cd95fbfdb Improve
https://github.com/2dust/v2rayNG/pull/2839
2024-02-14 18:52:05 +08:00
2dust c243fadacf Merge pull request #2839 from vfarid/add_fragment_preferences
Added fragment to the preferences settings
2024-02-14 17:31:48 +08:00
Vahid Farid 9c06412ceb Update V2rayConfig.kt 2024-02-14 11:20:25 +03:30
Vahid Farid 8d1f0d5df9 Update ServerConfig.kt 2024-02-14 11:20:20 +03:30
Το μοχθηρό ^_^ a035f42008 Merge pull request #9 from Malus-risus/renovate/android-actions-setup-android-3.x
Update android-actions/setup-android action to v3
2024-02-14 15:40:03 +08:00
Το μοχθηρό ^_^ 838fb041aa Merge pull request #8 from Malus-risus/renovate/actions-upload-artifact-4.x
Update actions/upload-artifact action to v4
2024-02-14 15:39:54 +08:00
Το μοχθηρό ^_^ 7fbab63227 Merge pull request #7 from Malus-risus/renovate/actions-setup-java-4.x
Update actions/setup-java action to v4
2024-02-14 15:39:43 +08:00
Το μοχθηρό ^_^ ad871723fe Merge pull request #6 from Malus-risus/renovate/actions-setup-go-5.x
Update actions/setup-go action to v5
2024-02-14 15:39:33 +08:00
Το μοχθηρό ^_^ 8887b44bf7 Merge pull request #5 from Malus-risus/renovate/actions-checkout-4.x
Update actions/checkout action to v4
2024-02-14 15:39:18 +08:00
Το μοχθηρό ^_^ 176beced3a Merge pull request #4 from Malus-risus/renovate/gradle-8.x
Update dependency gradle to v8.6
2024-02-14 15:39:01 +08:00
Vahid Farid a3561ddc6c Fixed and turened off fragmentOutbound function for future use 2024-02-14 11:06:41 +03:30
renovate[bot] 097bd06021 Update android-actions/setup-android action to v3 2024-02-14 07:30:38 +00:00
renovate[bot] dfdd1efcc8 Update actions/upload-artifact action to v4 2024-02-14 07:30:35 +00:00
renovate[bot] 1ac5a410d4 Update actions/setup-java action to v4 2024-02-14 07:30:32 +00:00
renovate[bot] 8c44e849c9 Update actions/setup-go action to v5 2024-02-14 07:30:29 +00:00
renovate[bot] 2684bd2af4 Update actions/checkout action to v4 2024-02-14 07:30:26 +00:00
renovate[bot] 01a860aab5 Update dependency gradle to v8.6 2024-02-14 07:30:22 +00:00
Το μοχθηρό ^_^ e1faf4e54e Merge pull request #1 from Malus-risus/renovate/configure
Configure Renovate
2024-02-14 15:13:34 +08:00
renovate[bot] 14dd4d6b99 Add renovate.json 2024-02-14 07:12:20 +00:00
Vahid Farid bab21bc8a5 Implemented fragment in V2rayConfigUtil.kt 2024-02-13 18:37:48 +03:30
Vahid Farid 8393b3ce86 Added relevant codes to handle fragment settings in SettingsActivity.kt 2024-02-13 18:36:40 +03:30
Vahid Farid e15eec9cff Added fragment fields to pref_settings.xml 2024-02-13 18:35:46 +03:30
Vahid Farid 32741ed7ab Added PREF_FRAGMENT_* to SettingsViewModel.kt 2024-02-13 16:44:17 +03:30
Vahid Farid 0d77a65bbb Make tag, mux & sockopt writable in V2rayConfig.kt 2024-02-13 16:43:29 +03:30
Vahid Farid 6eaac2d7e9 Added getFragmentOutbound function to ServerConfig.kt 2024-02-13 16:38:27 +03:30
Vahid Farid 496a0ec92c Added PREF_FRAGMENT_* to AppConfig.kt 2024-02-13 16:37:51 +03:30
Vahid Farid 826329e996 Added title_pref_fragment_* to strings.xml 2024-02-13 16:29:39 +03:30
Vahid Farid 4f57da4a38 Added fragment_packets to arrays.xml 2024-02-13 16:28:46 +03:30
2dust b3f49d0a34 Merge pull request #2836 from vfarid/fix_fragmentBean_add_dialerProxy
fix for SockoptBean & FragmentBean
2024-02-13 10:18:41 +08:00
2dust b78b370408 Merge pull request #2834 from hamidrezahy/thirdparty_asset_update
Automatic update of third party dat files - fix #2821
2024-02-13 10:09:49 +08:00
Vahid Farid 82afcdddd0 Added dialerProxy to SockoptBean and fixed default values if FragmentBean 2024-02-12 17:51:27 +03:30
hamidreza hematyar a33a698f38 handle upload file, improve asset ui, fix messages 2024-02-12 15:25:05 +03:30
hamidreza hematyar 3aff1800cd fix download assets functionality, fix strings.xml 2024-02-12 03:16:57 +03:30
hamidreza hematyar ea816ca981 add asset by url menu, ui and save logic 2024-02-12 00:37:07 +03:30
hamidreza hematyar a2bace4ede update strings.xml 2024-02-12 00:34:49 +03:30
2dust 5589d1058b Merge pull request #2831 from hamidrezahy/fix_translations
Add missing translations
2024-02-11 09:00:57 +08:00
hamidreza hematyar a12fc32ff0 Add missing translations 2024-02-10 21:57:21 +03:30
2dust ce2d1c5e0d Bug fix 2024-02-10 11:59:00 +08:00
2dust eb75666c85 Merge pull request #2827 from vfarid/master
added support for multiple custom configs in subscriptions + remarks
2024-02-10 09:54:47 +08:00
2dust 4b970cedcc Up build.gradle.kts 2024-02-10 09:51:29 +08:00
Vahid Farid fbd9d92f5e Update strings.xml 2024-02-10 01:22:07 +03:30
Vahid Farid cdaff4da06 added support for multiple custom configs in subscriptions + remarks
Accepting both single config and multiple configs i.e:
{customConfig} or [{customConfig1}, {customConfig2} ...]

Furtheremore added an optional 'remarks' field to v2rayConfig class to keep remote customConfig name.
2024-02-09 20:25:51 +03:30
2dust fbb17390f2 Up build.gradle.kts 2024-02-09 17:38:03 +08:00
2dust 52273db482 build.gradle 2 build.gradle.kts 2024-02-05 19:47:30 +08:00
2dust a6af25ae88 up 1.8.15 2024-02-04 16:49:12 +08:00
2dust 3ac89d68fb Up implementation 2024-02-04 16:46:01 +08:00
2dust 267a43fd97 Refactor 2024-02-04 12:28:58 +08:00
2dust 6d0384b6f1 Bug fix 2024-02-02 16:47:22 +08:00
2dust 7ae4be402f Merge pull request #2785 from maskedeken/fix-wg
MUX should be disabled when using wireguard
2024-01-25 16:26:41 +08:00
2dust 46f0b7be5b Merge pull request #2783 from admarty/master
Update Vietnamese translation.
2024-01-25 16:26:19 +08:00
Eken Chan b253f2d947 MUX should be disabled when using wireguard 2024-01-24 16:15:44 +08:00
admarty 444ade8afe Update Vietnamese translation 2024-01-23 19:27:46 +07:00
admarty 522dbdd170 Update Vietnamese translation. 2024-01-23 19:00:37 +07:00
2dust d89238e1aa up 1.8.14 2024-01-19 18:56:44 +08:00
2dust 52710020ea update subscription 4 custom configuration 2024-01-19 17:44:35 +08:00
2dust a18b8f4f2b Bug fix 2024-01-19 10:29:43 +08:00
2dust 01e0a6570d Merge pull request #2768 from solokot/master
Update Russian translation
2024-01-19 08:36:22 +08:00
solokot cd6ef8f062 Update Russian translation
MTU menu
2024-01-18 08:38:45 +03:00
2dust ea383c43bd Add share 4 Wireguard 2024-01-18 11:56:03 +08:00
2dust 68e08e3866 Merge pull request #2763 from admarty/master
Update vi translation
2024-01-18 09:57:21 +08:00
admarty 329ed26e85 Update vi translation 2024-01-17 12:56:39 +07:00
admarty c830a4cea4 Revert "Updated vi translation"
This reverts commit e7e088ec83.
2024-01-17 12:53:53 +07:00
admarty e7e088ec83 Updated vi translation 2024-01-17 12:46:48 +07:00
2dust cf6c814eb4 Add share 4 Wireguard 2024-01-16 18:49:00 +08:00
2dust 8f62e42ae8 Add Wireguard Mtu 2024-01-16 17:48:04 +08:00
2dust 89b4060ba2 Merge pull request #2748 from admarty/master
Improve vietnamese translation
2024-01-11 17:53:51 +08:00
admarty e4d4b329a0 fix typo 2024-01-11 13:03:53 +07:00
admarty 09c5f41995 Improve Vietnamese translation 2024-01-11 12:04:57 +07:00
admarty 02b25787c1 Improve Vietnamese translation 2024-01-11 00:48:28 +07:00
2dust cdb050b6c5 up 1.8.13 2024-01-08 09:13:42 +08:00
2dust 21f47b536d Bug fix 2024-01-07 17:15:09 +08:00
2dust f14484c986 Merge pull request #2733 from solokot/master
Russian translation
2024-01-07 16:31:38 +08:00
solokot 358dd78dc3 Russian translation
Small cosmetic fixes
2024-01-07 06:58:48 +00:00
2dust d79e072316 Merge pull request #2732 from admarty/master
Improve Vietnamese translation
2024-01-07 09:25:27 +08:00
2dust c24dee567a Modify background color 2024-01-07 09:24:48 +08:00
admarty 9f75bafcea Update strings.xml 2024-01-06 21:11:34 +07:00
admarty 2150452629 Update strings.xml 2024-01-06 20:50:50 +07:00
admarty 507a32a08e Improve Vietnamese translation 2024-01-06 20:22:52 +07:00
2dust 44dee8e850 Add main interface filter cache 2024-01-06 16:56:19 +08:00
2dust 5e4d9246c2 Merge pull request #2721 from mosayeb-a/fix-bug
fix a bug(ConcurrentModificationException)
2024-01-05 12:12:22 +08:00
mosayeb 9fe7419467 fix a bug(ConcurrentModificationException) 2024-01-03 11:15:00 -08:00
2dust 41b2251dfe Merge pull request #2718 from yuhan6665/wireguard
Wireguard can configure tun address
2024-01-02 12:06:08 +08:00
yuhan6665 73fad43573 Handle IPv6 address correctly 2024-01-01 22:16:07 -05:00
yuhan6665 4676717582 Wireguard can configure tun address
local tun address is also used by Wireguard server to check against its "allowedIPs" setting
2024-01-01 21:49:54 -05:00
2dust 834766e6e7 Merge pull request #2704 from yuhan6665/wireguard
Add Wireguard maunal config
2023-12-24 16:32:26 +08:00
yuhan6665 e304dce347 Add Wireguard maunal config
No support for export and import via links yet
2023-12-23 23:51:09 -05:00
2dust 61654aefeb Merge pull request #2690 from user09283/patch-1
Update strings.xml
2023-12-18 15:49:06 +08:00
user09283 1664aaa25b Update strings.xml
Update Vietnamese language
2023-12-18 00:41:47 +07:00
2dust 68f1f64f3d Merge pull request #2674 from mmrabbani/patch-1
Add HTTP3 to ALPN options
2023-12-14 09:20:11 +08:00
2dust 7bad57ca52 Merge pull request #2671 from solokot/master
Update Russian translation
2023-12-14 09:18:57 +08:00
MMR 1c8e1f0993 Add HTTP3 to ALPN options 2023-12-13 16:01:12 +03:30
solokot 6037ae6fc4 Update Russian translation 2023-12-09 18:59:33 +03:00
2dust dc1c5400b8 Add mux concurency setting 2023-12-07 17:49:09 +08:00
2dust 9ae4688171 Update build.yml 2023-12-05 17:52:45 +08:00
2dust e3f39234b2 Bug fix 2023-12-05 16:51:31 +08:00
2dust 0df0b2d6ac Merge pull request #2662 from solokot/master
Update Russian
2023-12-05 16:43:00 +08:00
2dust 9ce96d0591 up 1.8.12 2023-12-04 17:13:03 +08:00
2dust a7ef0618ba Adjust FOREGROUND_SERVICE_SPECIAL_USE 2023-12-04 17:11:53 +08:00
solokot cc9b083e5d Update Russian 2023-12-04 10:00:31 +03:00
2dust 5af322552c Merge pull request #2660 from yuhan6665/xudp
Use Android ID as XUDP basekey
2023-12-04 12:14:38 +08:00
yuhan6665 fc852281dd Use Android ID as XUDP basekey
Since Xray 1.8.1, XUDP pass basekey as the global ID. It can maintain the same UDP port on the proxy server outbound.
To enable maximum UDP connectivity, client should pass the device unique ID in the environment variable.
2023-12-03 22:06:06 -05:00
2dust 0b2f036a22 Up targetSdkVersion 34 2023-12-04 10:55:26 +08:00
2dust cf2becb5e9 Bug fix 2023-12-04 08:52:26 +08:00
2dust 82d8eba1b9 Merge pull request #2636 from hadi-norouzi/feature/update_sub
Fix minimum update interval for subscription auto update
2023-11-21 16:47:17 +08:00
Hadi Norouzi 286ad34d94 fix minimum subscription update interval 2023-11-20 21:25:35 +03:30
Hadi Norouzi ff0bc6594d Merge branch 'master' into feature/update_sub 2023-11-20 21:23:38 +03:30
Hadi Norouzi 7b8113aef1 Merge remote-tracking branch 'origin/feature/update_sub' into feature/update_sub
# Conflicts:
#	V2rayNG/app/build.gradle
2023-11-20 21:21:24 +03:30
2dust 81d2ef5db5 Merge pull request #2421 from Fangliding/master
自己写了个小破自动编译action
2023-11-18 18:56:13 +08:00
2dust bdea3ef88c up 1.8.11 2023-11-18 17:24:37 +08:00
2dust c62c86fc29 Adjust UI 2023-11-18 16:39:04 +08:00
2dust 59f698f755 Merge pull request #2614 from solokot/master
Update Russian translation
2023-11-18 16:32:06 +08:00
solokot 9ac979006e Update Russian translation 2023-11-17 20:44:48 +03:00
2dust da6291a965 Add menu for privacy policy 2023-11-17 16:29:32 +08:00
2dust bf6555e57c Update CR.md 2023-11-17 14:15:29 +08:00
2dust 35b114220e Update CR.md 2023-11-17 10:15:10 +08:00
Hadi Norouzi 800bb6a4e9 Merge branch 'master' of https://github.com/2dust/v2rayNG 2023-11-15 23:51:10 +03:30
2dust 2c80521f5b Merge pull request #2605 from solokot/master
Update Russian translation
2023-11-15 19:40:00 +08:00
2dust 6351ce5991 up 1.8.10 2023-11-15 19:35:16 +08:00
2dust 683362f0ee Fix mux enabled 2023-11-15 19:17:02 +08:00
2dust 84fc909339 Adjust UI 2023-11-15 17:22:16 +08:00
solokot 74171e26db Update Russian translation
Automatic update subscriptions
2023-11-15 11:20:55 +03:00
2dust f25c0cc890 Fix Automatic update subscriptions 2023-11-15 15:27:53 +08:00
2dust 29848053a4 Revert "Merge pull request #2603 from maskedeken/fix-mux"
This reverts commit 0d9856919e, reversing
changes made to 3dde6b0ca3.
2023-11-15 14:17:21 +08:00
2dust 0d9856919e Merge pull request #2603 from maskedeken/fix-mux
fix mux not working
2023-11-15 14:12:33 +08:00
Eken Chan 0ae7f2f7b3 fix mux not working 2023-11-14 15:42:00 +08:00
2dust 3dde6b0ca3 Fix Automatic update subscriptions 2023-11-14 09:32:05 +08:00
2dust 6b28208044 Fix Automatic update subscriptions 2023-11-13 18:00:00 +08:00
2dust 13f855e3c4 remove android.intent.category.LEANBACK_LAUNCHER 2023-11-13 12:42:57 +08:00
2dust c870595e98 Merge pull request #2596 from solokot/master
Russian translation
2023-11-13 12:39:24 +08:00
2dust ae19a3f68d Merge pull request #2471 from hadi-norouzi/feature/update_sub
add auto update subscriptions with interval
2023-11-13 12:38:54 +08:00
2dust 545afc41b3 Merge branch 'master' into feature/update_sub 2023-11-13 12:37:59 +08:00
solokot 7177d88144 Russian translation
Fix Google translate
2023-11-10 19:48:26 +03:00
2dust 294ed50afd Merge pull request #2592 from hossinasaadi/master
fix ServerActivity.kt toast
2023-11-10 15:19:28 +08:00
2dust 0105fe48f7 Merge pull request #2590 from yuhan6665/xudp
Add menu options for Mux
2023-11-10 15:18:44 +08:00
Hossin Asaadi c7ff23e3d5 Update ServerActivity.kt
fix trojan, shadowsocks password toast
2023-11-08 16:30:04 +04:00
yuhan6665 23e9d7fde5 Add menu options for Mux 2023-11-05 23:16:01 -05:00
Hadi Norouzi c93edd8875 Merge branch '2dust:master' into master 2023-10-05 20:13:50 +03:30
2dust bde37e38a7 up 1.8.9 2023-09-30 16:29:37 +08:00
2dust c401d63d2f Merge pull request #2543 from yuhan6665/rprx-ui
Unified base activity for status bar icon color
2023-09-30 14:26:28 +08:00
yuhan6665 52416dd43d Unified base activity for status bar icon color 2023-09-29 19:59:06 -04:00
2dust 59bd7128ae Merge pull request #2540 from MrIbrahem/patch-1
Update strings.xml
2023-09-29 13:58:58 +08:00
2dust 87f16467bb Merge pull request #2539 from MrIbrahem/update
Update strings.xml
2023-09-29 13:58:46 +08:00
ibrahem Qasim bed0fd00bd Update strings.xml
update arabic string
2023-09-28 00:32:18 +03:00
ibrahem Qasim 0672af98f8 Update strings.xml 2023-09-28 00:23:40 +03:00
2dust 2341eceb65 up 1.8.8 2023-09-25 11:32:57 +08:00
2dust ec5f7245bf up 1.8.7 2023-09-25 11:20:21 +08:00
Hadi Norouzi caa2edcf05 bump version 2023-09-12 22:32:41 +03:30
Hadi Norouzi dbe78d0aa5 Merge remote-tracking branch 'origin/master' into feature/update_sub 2023-09-12 22:28:15 +03:30
Hadi Norouzi 71f2f590a7 build a version from auto update subscription 2023-09-12 22:20:28 +03:30
Hadi Norouzi ed26120581 Merge branch '2dust:master' into master 2023-09-12 22:08:12 +03:30
风扇滑翔翼 71bd684b46 Update build.yml 2023-09-08 23:51:26 +08:00
风扇滑翔翼 02ae19f0c7 Merge remote-tracking branch 'upstream/master' 2023-09-08 15:25:46 +00:00
2dust a46b0d58eb Merge pull request #2500 from MrIbrahem/master
Update arrays.xml to support Arabic language
2023-09-06 07:21:10 +08:00
ibrahem Qasim d1262d169b Update arrays.xml 2023-09-01 17:39:16 +03:00
2dust 4a85c95b02 Merge pull request #2485 from y67h41/fix-/0-CIDR
Make /0 CIDR parsed as IP
2023-09-01 22:33:13 +08:00
2dust a368927b9f Merge pull request #2475 from user09283/master-1
Update strings.xml
2023-09-01 22:31:51 +08:00
2dust 80feb69af5 Merge pull request #2460 from MrIbrahem/master
Create arabic translations
2023-09-01 22:31:38 +08:00
2dust c09f0d5787 Merge pull request #2478 from yuhan6665/rprx-ui
A freshing new UI
2023-09-01 22:31:13 +08:00
y67h41 ff667546b8 Update ExampleUnitTest.kt 2023-08-24 17:55:12 +08:00
y67h41 6a255cdfa4 Make /0 CIDR be parsed as IP 2023-08-24 17:36:49 +08:00
yuhan6665 2b5784df6f Update UI: status bar icon color 2023-08-20 22:09:30 -04:00
user09283 a9665dd2d8 Update strings.xml 2023-08-19 13:11:14 +07:00
user09283 1e52877e93 Update strings.xml
Update Vietnamese language
2023-08-19 13:10:08 +07:00
Hadi Norouzi 062c0d8ddb add auto update subscriptions with interval 2023-08-17 21:06:22 +03:30
yuhan6665 a7a70b448f Update UI: tweak fab color 2023-08-15 20:14:43 -04:00
yuhan6665 3e52aeb804 Update UI: custom font 2023-08-13 23:10:20 -04:00
yuhan6665 4b4f5d145b Update UI: a concept for launching icon 2023-08-13 15:01:07 -04:00
风扇滑翔翼 775fa5ea62 update and remove unnecessary step 2023-08-12 00:54:19 +08:00
ibrahem Qasim 34d8329c8a Delete plurals.xml 2023-08-10 21:12:02 +03:00
ibrahem Qasim 2e7ae732aa Update strings.xml 2023-08-10 21:10:10 +03:00
ibrahem Qasim 424287e258 Rename s.xml to strings.xml 2023-08-10 13:26:25 +03:00
ibrahem Qasim 40d03bbb96 Delete strings.xml 2023-08-10 13:26:05 +03:00
ibrahem Qasim d768694445 Update s.xml 2023-08-10 13:18:58 +03:00
ibrahem Qasim 56c7f2ef69 Update s.xml 2023-08-10 13:13:15 +03:00
ibrahem Qasim 3839b0c59c Update s.xml 2023-08-10 13:07:36 +03:00
ibrahem Qasim 77cab14ae8 Create s.xml 2023-08-10 12:58:37 +03:00
ibrahem Qasim b0dbd4c7ca Create plurals.xml 2023-08-10 12:38:18 +03:00
ibrahem Qasim 50ca2e0e69 Create strings.xml 2023-08-10 12:36:43 +03:00
yuhan6665 0bf8beda94 Update UI: tweak after review 2023-08-09 21:38:20 -04:00
yuhan6665 4d334929c9 Update UI: complete change for dark mode 2023-08-07 14:32:41 -04:00
yuhan6665 ccfdf096f9 Update UI dark mode 2023-08-06 22:44:57 -04:00
yuhan6665 b4f2af2778 Update UI: header image 2023-08-06 21:53:14 -04:00
yuhan6665 32b9e4855c Update UI: clean up theme 2023-08-06 18:41:36 -04:00
yuhan6665 bdbce5147e Update UI: menu icons 2023-08-06 17:37:24 -04:00
yuhan6665 a4833506ae Update UI: launching icon 2023-08-06 16:55:29 -04:00
yuhan6665 d4e8072248 Update UI for white theme 2023-08-06 16:09:14 -04:00
yuhan6665 207ca93735 Fix lint by adding missing translation 2023-08-06 10:31:37 -04:00
2dust 99307ab8f0 up 1.8.6 2023-07-30 11:05:31 +08:00
风扇滑翔翼 a5bb39ac8a 允许手动指定编译时的Xray core版本
and update sth
2023-07-23 11:16:06 +08:00
风扇滑翔翼 073c7c0410 新增触发条件 2023-07-23 03:29:07 +08:00
风扇滑翔翼 7e88e3ba4f 新建Build action 2023-07-23 03:23:02 +08:00
2dust 1ec23a7b39 Merge pull request #2302 from dep4/fix/package-name
AppConfig: use current package name to receive broadcasts
2023-05-23 06:15:07 +08:00
Bruce Mills 685f9e220c AppConfig: use current package name to receive broadcasts 2023-05-21 15:54:25 -04:00
2dust e77b7eb52e Merge pull request #2290 from CUMOON/master
Correction of Persian translation
2023-05-21 08:35:38 +08:00
2dust 29014704e0 Merge pull request #2288 from hadi-norouzi/feature/tv
feature: add leanback for android tv compatiblity
2023-05-21 08:35:13 +08:00
Moon b3570d9c0b Correction of Persian translation 2023-05-17 12:49:41 +03:30
Hadi Norouzi 6e427cee82 feature: add leanback for android tv compatiblity 2023-05-16 11:33:29 +03:30
2dust 0a1b6d00d9 Merge pull request #2284 from solokot/master
RU: Improvements for google translation
2023-05-15 20:24:19 +08:00
solokot 00ffe66f36 RU: Improvements for google translation 2023-05-15 09:13:11 +03:00
2dust 63661fbdaa Merge pull request #2273 from hadi-norouzi/master
[Feature] separate package names for pre release and dev builds
2023-05-13 19:25:53 +08:00
Hadi Norouzi 4d4a4543c5 feature: import subscription from v2rayng://install-sub deeplink 2023-05-11 20:42:15 +03:30
Hadi Norouzi 2349805968 feature: separate package names for pre release and dev builds 2023-05-10 20:16:11 +03:30
2dust bdbd0b154e up 1.8.5 2023-05-07 15:35:42 +08:00
2dust 653137ec58 bug fixes 2023-05-07 15:34:11 +08:00
2dust 39f65850be Merge pull request #2258 from Amir-yazdanmanesh/master
Fix persian translation
2023-05-07 15:19:33 +08:00
2dust 2d8db97417 Merge branch 'master' into master 2023-05-07 15:19:25 +08:00
2dust 17a6f80798 Merge pull request #2255 from hadi-norouzi/feature/share_sub
Feature/share sub
2023-05-07 15:18:28 +08:00
2dust 381fe859ff Merge pull request #2254 from hadi-norouzi/master
Fix status bar and navigation bar, remove FooterView TouchHelper
2023-05-07 15:14:04 +08:00
am.yazdanmanesh e2bdf17b82 Fix persian translation 2023-05-05 21:54:18 +03:30
Hadi Norouzi d20afc6801 Share subscription implemented 2023-05-04 18:03:50 +03:30
Hadi Norouzi 54046a27e6 Fix fa strings and test layout direction 2023-05-04 17:04:35 +03:30
Hadi Norouzi 36c000d18a Fix status bar and navigation bar, remove FooterView TouchHelper 2023-05-04 16:34:41 +03:30
2dust 2f3c2cf4d5 up 1.8.4 2023-04-29 20:46:28 +08:00
2dust 892358d5d8 Merge pull request #2227 from AlirezaIvaz/master
Add themed icon support
2023-04-27 09:40:08 +08:00
Alireza Ivaz 6dced903cd Add themed icon support 2023-04-26 17:05:11 +03:30
2dust f8a98a426e Merge pull request #2213 from solokot/master
Updated Russian translation
2023-04-22 14:40:57 +08:00
solokot 011506e99f Updated Russian translation 2023-04-21 21:33:22 +03:00
2dust 966151d3fe bug fixes 2023-04-19 19:45:11 +08:00
2dust 247f4db77e up 1.8.3 2023-04-18 09:51:49 +08:00
2dust 9a661bc401 Use Google code scanner 2023-04-17 10:28:34 +08:00
2dust f5f1b3816c Refactor createQRCode 2023-04-16 19:58:51 +08:00
2dust 3971c9badc use Exception 2023-04-16 19:56:44 +08:00
2dust 390fbf046b Merge pull request #2130 from pouriaksrvi/master
[New Feature] UX enhancement - Reversed the order of server list
2023-04-06 08:28:54 +08:00
Programmer ec7ba59528 Bug Fix - Fixed the order of subscription import
Subscription server list should not be reversed when it is being imported. Fixed this problem.
2023-04-03 04:12:01 +03:30
Programmer 838941c6a4 Merge branch '2dust:master' into master 2023-04-03 04:00:52 +03:30
2dust a0ec764b64 Merge pull request #2142 from solokot/master
Updated Russian translation
2023-04-02 09:32:29 +08:00
2dust 26f1f7099b up 1.8.2 2023-04-01 18:08:11 +08:00
2dust 613e9ac8bf Add permission POST_NOTIFICATIONS and READ_MEDIA_IMAGES 2023-03-31 13:48:40 +08:00
2dust ea979f02b5 update gradle 2023-03-30 20:45:37 +08:00
2dust 383f55f809 update org.jetbrains.kotlin.android to 1.8.0 2023-03-30 13:21:36 +08:00
2dust ce61f177dc Adjust build gradle 2023-03-30 11:54:15 +08:00
2dust 6a64157537 update kotlinx-coroutines to 1.6.4 2023-03-29 16:15:43 +08:00
2dust de83302c8a update buildtoolsversion & targetSdkversion to 33 2023-03-29 15:54:35 +08:00
2dust 68553d3807 update kotlin version to 1.7.20 2023-03-29 15:25:54 +08:00
solokot 4cfe3394b1 Updated Russian translation 2023-03-29 09:35:28 +03:00
Programmer ae9aa75ba0 New Feature
Fixed incorrect default order of server list as described in the issue https://github.com/2dust/v2rayNG/issues/2116
The order was problematic for users who add new servers more frequently
2023-03-26 06:27:52 +00:00
2dust 3d7ed12d4b Added the function of deleting duplicate configurations 2023-03-25 21:16:21 +08:00
2dust f70be5bce9 routing domainMatcher is set to null 2023-03-25 18:10:29 +08:00
2dust 405667697e REALITY show false 2023-03-25 18:09:53 +08:00
2dust 3aeda7de81 Merge pull request #2122 from justlovediaodiao/master
fix a crash when adding custom config
2023-03-24 09:15:01 +08:00
yangxing 8a775d662a fix a crash when adding empty custom config 2023-03-23 22:25:07 +08:00
2dust ec0ccbca76 up 1.8.1 2023-03-21 17:17:51 +08:00
2dust 6cdcbb0096 Add REALITY share link 2023-03-21 10:04:03 +08:00
2dust 79e3881704 hide alpn in reality 2023-03-21 09:19:21 +08:00
2dust 4cf2d429f0 Adjust string 2023-03-21 09:18:46 +08:00
2dust fa51952bfa up 1.8.0 2023-03-10 11:17:25 +08:00
2dust b2c4c0a67e Adjust string 2023-03-10 10:00:34 +08:00
2dust cf2c58637b Merge pull request #2036 from yuhan6665/reality
Add reality and remove legacy xtls settings
2023-02-21 20:26:58 +08:00
yuhan6665 a2c262441e Add reality and remove legacy xtls settings
No sharing support for Reality yet
2023-02-20 21:25:33 -05:00
2dust 237be79680 up 1.7.38 2023-02-08 20:07:30 +08:00
2dust 441a64b8c6 add more fingerprint 2023-02-08 20:07:12 +08:00
2dust 2128488658 up 1.7.37 (rollback core to 1.7.2) 2023-02-03 12:04:39 +08:00
2dust d9dc8317f5 up 1.7.36 2023-02-03 10:47:28 +08:00
2dust 6eda2180e4 Update strings.xml 2023-02-03 10:42:24 +08:00
2dust 57a2f0bebf share link to add fingerprint 2023-02-03 10:11:40 +08:00
2dust 1fd3ccaa0c Merge pull request #1959 from user09283/patch-2
Update strings.xml
2023-01-29 20:23:23 +08:00
user09283 528597d0c7 Update strings.xml 2023-01-29 16:22:31 +07:00
user09283 37411e3e6b Update strings.xml 2023-01-29 16:13:38 +07:00
2dust 6d681b92aa up 1.7.35 2023-01-28 15:52:25 +08:00
2dust 73fae63905 Merge pull request #1942 from user09283/patch-1
Update strings.xml (vi)
2023-01-28 15:38:04 +08:00
2dust 216710974a Merge pull request #1920 from ysy950803/master
Replace colors hard code with values-night.
2023-01-28 15:37:53 +08:00
user09283 f3bbef7412 Update strings.xml 2023-01-26 14:03:47 +07:00
user09283 fa74d9c598 Update strings.xml 2023-01-24 21:00:51 +07:00
user09283 113364af26 Update strings.xml 2023-01-24 20:51:43 +07:00
Sylvester Yao 2990db305e Replace colors hard code with values-night. 2023-01-13 12:00:59 +08:00
2dust db8942fe5a up 1.7.34 2023-01-12 15:42:47 +08:00
2dust 402d63bd9a . 2023-01-12 14:35:38 +08:00
369 changed files with 14837 additions and 11686 deletions
@@ -1,3 +1,8 @@
---
name: v2rayNG程序问题
about: 创建一个报告来帮助我们改进
---
在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。
@@ -14,7 +19,8 @@
### 日志信息
<details>
通过 `adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main` 获取日志。请自行删减日志中可能出现的敏感信息。
通过`adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main`获取日志。请自行删减日志中可能出现的敏感信息。
如果问题可重现,建议先执行`adb logcat -c`清空系统日志再执行上述命令,再操作重现问题。
```
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: V2Ray程序问题
url: https://github.com/v2fly/v2ray-core/
about: 如果您有V2Ray而非v2rayNG的问题,请至这个链接讨论。
+63
View File
@@ -0,0 +1,63 @@
name: Build APK
on:
push:
workflow_dispatch:
inputs:
XRAY_CORE_VERSION:
description: 'Xray core version or commit hash'
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Golang
uses: actions/setup-go@v5
with:
go-version: '1.22.2'
- name: Install gomobile
run: |
go install golang.org/x/mobile/cmd/gomobile@latest
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Setup Android environment
uses: android-actions/setup-android@v3
- name: Build dependencies
run: |
mkdir ${{ github.workspace }}/build
cd ${{ github.workspace }}/build
git clone --depth=1 -b main https://github.com/2dust/AndroidLibXrayLite.git
cd AndroidLibXrayLite
go get github.com/xtls/xray-core@${{ github.event.inputs.XRAY_CORE_VERSION }} || true
gomobile init
go mod tidy -v
gomobile bind -v -androidapi 21 -ldflags='-s -w' ./
cp *.aar ${{ github.workspace }}/V2rayNG/app/libs/
- name: Build APK
run: |
cd ${{ github.workspace }}/V2rayNG
chmod 755 gradlew
./gradlew assembleDebug
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: apk
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/debug/
+3 -9
View File
@@ -1,11 +1,5 @@
V2rayNG/app/src/main/res/layout/activity_inapp_buy.xml
V2rayNG/app/src/main/assets/geoip.dat
V2rayNG/app/src/main/assets/geosite.dat
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/gradle/wrapper/gradle-wrapper.properties
*.dat
*.jks
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/app/release/output.json
V2rayNG/app/release/output.json
.idea/
.gradle/
-26
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
-63
View File
@@ -1,63 +0,0 @@
package CoreI
import (
v2core "v2ray.com/core"
)
type Status struct {
IsRunning bool
PackageName string
Vpoint v2core.Server
}
func CheckVersion() int {
return 20
}
func (v *Status) GetDataDir() string {
return v.PackageName
}
func (v *Status) GetApp(name string) string {
return v.PackageName + name
}
func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {
ret = []string{"--netif-ipaddr",
"26.26.26.2",
"--netif-netmask",
"255.255.255.252",
"--socks-server-addr",
"127.0.0.1:10808",
"--tunmtu",
"1500",
"--loglevel",
"notice",
"--enable-udprelay",
"--sock-path",
v.GetDataDir() + "sock_path",
}
if enableIPv6 {
ret = append(ret, "--netif-ip6addr", "da26:2626::2")
}
if localDNS {
ret = append(ret, "--dnsgw", "127.0.0.1:10807")
}
return
}
func (v *Status) GetVPNSetupArg(localDNS bool, enableIPv6 bool) (ret string) {
ret = "m,1500 a,26.26.26.1,30 r,0.0.0.0,0"
if enableIPv6 {
ret += " a,da26:2626::1,126 r,::,0"
}
if localDNS {
ret += " d,26.26.26.2"
}
return
}
-34
View File
@@ -1,34 +0,0 @@
pb:
go get -u github.com/golang/protobuf/protoc-gen-go
@echo "pb Start"
asset:
bash gen_assets.sh download
mkdir assets
cp -v data/*.dat assets/
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geosite.dat > geosite.dat
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geoip.dat > geoip.dat
shippedBinary:
cd shippedBinarys; $(MAKE) shippedBinary
fetchDep:
-go get github.com/2dust/AndroidLibV2rayLite
go get github.com/2dust/AndroidLibV2rayLite
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
all: asset pb shippedBinary fetchDep
@echo DONE
@@ -1,87 +0,0 @@
package Escort
import (
"os"
"os/exec"
"time"
"log"
"github.com/2dust/AndroidLibV2rayLite/CoreI"
)
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, sendFd func() int) {
log.Println(proc, pt)
count := 0
for count <= 42 {
cmd := exec.Command(proc, pt...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if len(additionalEnv) > 0 {
//additionalEnv := "FOO=bar"
newEnv := append(os.Environ(), additionalEnv)
cmd.Env = newEnv
}
if err := cmd.Start(); err != nil {
log.Println("EscortRun cmd.Start err", err)
goto CMDERROR
}
if v.escortProcess == nil {
log.Println("EscortRun v.escortProcess nil")
break
}
*v.escortProcess = append(*v.escortProcess, cmd.Process)
log.Println("EscortRun Waiting....")
if count > 0 {
go func() {
time.Sleep(time.Second)
sendFd()
}()
}
if err := cmd.Wait(); err != nil {
log.Println("EscortRun cmd.Wait err:", err)
}
CMDERROR:
if v.Status.IsRunning {
log.Println("EscortRun Unexpected Exit, Restart now.")
count++
} else {
log.Println("EscortRun Exit")
break
}
}
}
func (v *Escorting) EscortingUp() {
if v.escortProcess != nil {
return
}
v.escortProcess = new([](*os.Process))
}
func (v *Escorting) EscortingDown() {
if v.escortProcess == nil {
return
}
log.Println("EscortingDown() Killing all escorted process ")
for _, pr := range *v.escortProcess {
pr.Kill()
if _, err := pr.Wait(); err != nil {
log.Println("EscortingDown pr.Wait err:", err)
}
}
v.escortProcess = nil
}
type Escorting struct {
escortProcess *[](*os.Process)
Status *CoreI.Status
}
+19
View File
@@ -1 +1,20 @@
# AndroidLibV2rayLite
### Preparation
- latest Ubuntu environment
- At lease 30G free space
- Get Repo [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
### Prepare Go
- Go to https://golang.org/doc/install and install latest go
- Make sure `go version` works as expected
### Prepare gomobile
- Go to https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile and install gomobile
- export PATH=$PATH:~/go/bin
- Make sure `gomobile init` works as expected
### Prepare NDK
- Go to https://developer.android.com/ndk/downloads and install latest NDK
- export PATH=$PATH:<wherever you ndk is located>
- Make sure `ndk-build -v` works as expected
### Make
- sudo apt install make
- Read and understand [build script](https://github.com/2dust/AndroidLibV2rayLite/blob/master/Makefile)
@@ -1,279 +0,0 @@
package VPN
import (
"context"
"errors"
"fmt"
"log"
"net"
"os"
"strings"
"sync"
"time"
"golang.org/x/sys/unix"
v2net "v2ray.com/core/common/net"
v2internet "v2ray.com/core/transport/internet"
)
type protectSet interface {
Protect(int) int
}
type resolved struct {
domain string
IPs []net.IP
Port int
ipIdx uint8
ipLock sync.Mutex
lastSwitched time.Time
}
// NextIP switch to another resolved result.
// there still be race-condition here if multiple err concurently occured
// may cause idx keep switching,
// but that's an outside error can hardly handled here
func (r *resolved) NextIP() {
r.ipLock.Lock()
defer r.ipLock.Unlock()
if len(r.IPs) > 1 {
// throttle, don't switch too quickly
now := time.Now()
if now.Sub(r.lastSwitched) < time.Second*5 {
log.Println("switch too quickly")
return
}
r.lastSwitched = now
r.ipIdx++
} else {
return
}
if r.ipIdx >= uint8(len(r.IPs)) {
r.ipIdx = 0
}
cur := r.currentIP()
log.Printf("switched to next IP: %s", cur)
}
func (r *resolved) currentIP() net.IP {
if len(r.IPs) > 0 {
return r.IPs[r.ipIdx]
}
return nil
}
// NewPreotectedDialer ...
func NewPreotectedDialer(p protectSet) *ProtectedDialer {
d := &ProtectedDialer{
// prefer native lookup on Android
resolver: &net.Resolver{PreferGo: false},
protectSet: p,
}
return d
}
// ProtectedDialer ...
type ProtectedDialer struct {
currentServer string
resolveChan chan struct{}
vServer *resolved
resolver *net.Resolver
protectSet
}
func (d *ProtectedDialer) IsVServerReady() bool {
return (d.vServer != nil)
}
func (d *ProtectedDialer) PrepareResolveChan() {
d.resolveChan = make(chan struct{})
}
func (d *ProtectedDialer) ResolveChan() <-chan struct{} {
return d.resolveChan
}
// simplicated version of golang: internetAddrList in src/net/ipsock.go
func (d *ProtectedDialer) lookupAddr(addr string) (*resolved, error) {
var (
err error
host, port string
portnum int
)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if host, port, err = net.SplitHostPort(addr); err != nil {
log.Printf("PrepareDomain SplitHostPort Err: %v", err)
return nil, err
}
if portnum, err = d.resolver.LookupPort(ctx, "tcp", port); err != nil {
log.Printf("PrepareDomain LookupPort Err: %v", err)
return nil, err
}
addrs, err := d.resolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
if len(addrs) == 0 {
return nil, fmt.Errorf("domain %s Failed to resolve", addr)
}
IPs := make([]net.IP, len(addrs))
for i, ia := range addrs {
IPs[i] = ia.IP
}
rs := &resolved{
domain: host,
IPs: IPs,
Port: portnum,
}
return rs, nil
}
// PrepareDomain caches direct v2ray server host
func (d *ProtectedDialer) PrepareDomain(domainName string, closeCh <-chan struct{}) {
log.Printf("Preparing Domain: %s", domainName)
d.currentServer = domainName
defer close(d.resolveChan)
maxRetry := 10
for {
if maxRetry == 0 {
log.Println("PrepareDomain maxRetry reached. exiting.")
return
}
resolved, err := d.lookupAddr(domainName)
if err != nil {
maxRetry--
log.Printf("PrepareDomain err: %v\n", err)
select {
case <-closeCh:
log.Printf("PrepareDomain exit due to v2ray closed")
return
case <-time.After(time.Second * 2):
}
continue
}
d.vServer = resolved
log.Printf("Prepare Result:\n Domain: %s\n Port: %d\n IPs: %v\n",
resolved.domain, resolved.Port, resolved.IPs)
return
}
}
func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
switch network {
case v2net.Network_TCP:
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP)
case v2net.Network_UDP:
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
default:
err = fmt.Errorf("unknow network")
}
return
}
// 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) {
network := dest.Network.SystemString()
Address := dest.NetAddr()
// v2ray server address,
// try to connect fixed IP if multiple IP parsed from domain,
// and switch to next IP if error occurred
if strings.Compare(Address, d.currentServer) == 0 {
if d.vServer == nil {
log.Println("Dial pending prepare ...", Address)
<-d.resolveChan
// user may close connection during PrepareDomain,
// fast return release resources.
if d.vServer == nil {
return nil, fmt.Errorf("fail to prepare domain %s", d.currentServer)
}
}
fd, err := d.getFd(dest.Network)
if err != nil {
return nil, err
}
curIP := d.vServer.currentIP()
conn, err := d.fdConn(ctx, curIP, d.vServer.Port, fd)
if err != nil {
d.vServer.NextIP()
return nil, err
}
log.Printf("Using Prepared: %s", curIP)
return conn, nil
}
// v2ray connecting to "domestic" servers, no caching results
log.Printf("Not Using Prepared: %s,%s", network, Address)
resolved, err := d.lookupAddr(Address)
if err != nil {
return nil, err
}
fd, err := d.getFd(dest.Network)
if err != nil {
return nil, err
}
// use the first resolved address.
// the result IP may vary, eg: IPv6 addrs comes first if client has ipv6 address
return d.fdConn(ctx, resolved.IPs[0], resolved.Port, fd)
}
func (d *ProtectedDialer) fdConn(ctx context.Context, ip net.IP, port int, fd int) (net.Conn, error) {
defer unix.Close(fd)
// call android VPN service to "protect" the fd connecting straight out
d.Protect(fd)
sa := &unix.SockaddrInet6{
Port: port,
}
copy(sa.Addr[:], ip)
if err := unix.Connect(fd, sa); err != nil {
log.Printf("fdConn unix.Connect err, Close Fd: %d Err: %v", fd, err)
return nil, err
}
file := os.NewFile(uintptr(fd), "Socket")
if file == nil {
// returned value will be nil if fd is not a valid file descriptor
return nil, errors.New("fdConn fd invalid")
}
defer file.Close()
//Closing conn does not affect file, and closing file does not affect conn.
conn, err := net.FileConn(file)
if err != nil {
log.Printf("fdConn FileConn Close Fd: %d Err: %v", fd, err)
return nil, err
}
return conn, nil
}
@@ -1,151 +0,0 @@
package VPN
import (
"bufio"
"context"
"fmt"
"net"
"sync"
"testing"
"time"
v2net "v2ray.com/core/common/net"
)
type fakeSupportSet struct{}
func (f fakeSupportSet) Protect(int) int {
return 0
}
func TestProtectedDialer_PrepareDomain(t *testing.T) {
type args struct {
domainName string
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
{"", args{"baidu.com:80"}},
// {"", args{"cloudflare.com:443"}},
// {"", args{"apple.com:443"}},
// {"", args{"110.110.110.110:443"}},
// {"", args{"[2002:1234::1]:443"}},
}
d := NewPreotectedDialer(fakeSupportSet{})
for _, tt := range tests {
ch := make(chan struct{})
t.Run(tt.name, func(t *testing.T) {
go d.PrepareDomain(tt.args.domainName, ch)
time.Sleep(time.Second)
go d.vServer.NextIP()
t.Log(d.vServer.currentIP())
})
}
time.Sleep(time.Second)
}
func TestProtectedDialer_Dial(t *testing.T) {
tests := []struct {
name string
wantErr bool
}{
// TODO: Add test cases.
{"baidu.com:80", false},
{"cloudflare.com:80", false},
{"172.16.192.11:80", true},
// {"172.16.192.10:80", true},
// {"[2fff:4322::1]:443", true},
// {"[fc00::1]:443", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ch := make(chan struct{})
d := NewPreotectedDialer(fakeSupportSet{})
d.currentServer = tt.name
go d.PrepareDomain(tt.name, ch)
var wg sync.WaitGroup
dial := func() {
defer wg.Done()
dest, _ := v2net.ParseDestination("tcp:" + tt.name)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
conn, err := d.Dial(ctx, nil, dest, nil)
if err != nil {
t.Log(err)
return
}
_host, _, _ := net.SplitHostPort(tt.name)
fmt.Fprintf(conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", _host))
status, err := bufio.NewReader(conn).ReadString('\n')
t.Logf("%#v, %#v\n", status, err)
conn.Close()
}
for n := 0; n < 3; n++ {
wg.Add(1)
go dial()
// time.Sleep(time.Millisecond * 10)
// d.pendingMap[tt.name] = make(chan struct{})
}
wg.Wait()
})
}
}
func Test_resolved_NextIP(t *testing.T) {
type fields struct {
domain string
IPs []net.IP
Port int
}
tests := []struct {
name string
fields fields
}{
// TODO: Add test cases.
{"test1",
fields{
domain: "www.baidu.com",
IPs: []net.IP{
net.ParseIP("1.2.3.4"),
net.ParseIP("4.3.2.1"),
net.ParseIP("1234::1"),
net.ParseIP("4321::1"),
},
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &resolved{
domain: tt.fields.domain,
IPs: tt.fields.IPs,
Port: tt.fields.Port,
}
t.Logf("%v", r.IPs)
t.Logf("%v", r.currentIP())
r.NextIP()
t.Logf("%v", r.currentIP())
r.NextIP()
t.Logf("%v", r.currentIP())
r.NextIP()
t.Logf("%v", r.currentIP())
time.Sleep(3 * time.Second)
r.NextIP()
t.Logf("%v", r.currentIP())
time.Sleep(5 * time.Second)
r.NextIP()
t.Logf("%v", r.currentIP())
})
}
}
-47
View File
@@ -1,47 +0,0 @@
#!/bin/bash
set -o errexit
set -o pipefail
set -o nounset
# Set magic variables for current file & dir
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
if [[ ! -d $NDK_HOME ]]; then
echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME"
exit 1
fi
TMPDIR=$(mktemp -d)
clear_tmp () {
rm -rf $TMPDIR
}
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp; exit 1' ERR INT
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
$NDK_HOME/ndk-build \
NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=./tun2socks.mk \
APP_ABI=all \
APP_PLATFORM=android-19 \
NDK_LIBS_OUT=$TMPDIR/libs \
NDK_OUT=$TMPDIR/tmp \
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/shippedBinarys/ArchDep/arm/
install -v -m755 libs/arm64-v8a/tun2socks $__dir/shippedBinarys/ArchDep/arm64/
install -v -m755 libs/x86/tun2socks $__dir/shippedBinarys/ArchDep/386/
install -v -m755 libs/x86_64/tun2socks $__dir/shippedBinarys/ArchDep/amd64/
popd
pushd $__dir/shippedBinarys
make clean && make shippedBinary
popd
rm -rf $TMPDIR
-81
View File
@@ -1,81 +0,0 @@
#!/bin/bash
set -o errexit
set -o pipefail
set -o nounset
# Set magic variables for current file & dir
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
DATADIR=${__dir}/data
compile_dat () {
local TMPDIR=$(mktemp -d)
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; rm -rf $TMPDIR; trap ERR; exit 1' ERR
local GEOSITE=${GOPATH}/src/github.com/v2ray/domain-list-community
if [[ -d ${GEOSITE} ]]; then
cd ${GEOSITE} && git pull
else
mkdir -p ${GEOSITE}
cd ${GEOSITE} && git clone https://github.com/v2ray/domain-list-community.git .
fi
go run main.go
if [[ -e dlc.dat ]]; then
rm -f $DATADIR/geosite.dat
mv dlc.dat $DATADIR/geosite.dat
echo "----------> geosite.dat updated."
else
echo "----------> geosite.dat failed to update."
fi
if [[ ! -x $GOPATH/bin/geoip ]]; then
go get -v -u github.com/v2ray/geoip
fi
cd $TMPDIR
curl -L -O http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country-CSV.zip
unzip -q GeoLite2-Country-CSV.zip
mkdir geoip && find . -name '*.csv' -exec mv -t ./geoip {} +
$GOPATH/bin/geoip \
--country=./geoip/GeoLite2-Country-Locations-en.csv \
--ipv4=./geoip/GeoLite2-Country-Blocks-IPv4.csv \
--ipv6=./geoip/GeoLite2-Country-Blocks-IPv6.csv
if [[ -e geoip.dat ]]; then
rm -f $DATADIR/geoip.dat
mv ./geoip.dat $DATADIR/geoip.dat
echo "----------> geoip.dat updated."
else
echo "----------> geoip.dat failed to update."
fi
trap ERR
return 0
}
download_dat () {
wget -qO - https://api.github.com/repos/v2ray/geoip/releases/latest \
| grep browser_download_url | cut -d '"' -f 4 \
| wget -i - -O $DATADIR/geoip.dat
wget -qO - https://api.github.com/repos/v2ray/domain-list-community/releases/latest \
| grep browser_download_url | cut -d '"' -f 4 \
| wget -i - -O $DATADIR/geosite.dat
}
ACTION="${1:-}"
if [[ -z $ACTION ]]; then
ACTION=download
fi
case $ACTION in
"download") download_dat;;
"compile") compile_dat;;
esac
-255
View File
@@ -1,255 +0,0 @@
package libv2ray
import (
"fmt"
"io"
"log"
"os"
"strings"
"sync"
"github.com/2dust/AndroidLibV2rayLite/CoreI"
"github.com/2dust/AndroidLibV2rayLite/Process/Escort"
"github.com/2dust/AndroidLibV2rayLite/VPN"
"github.com/2dust/AndroidLibV2rayLite/shippedBinarys"
mobasset "golang.org/x/mobile/asset"
v2core "v2ray.com/core"
v2filesystem "v2ray.com/core/common/platform/filesystem"
v2stats "v2ray.com/core/features/stats"
v2serial "v2ray.com/core/infra/conf/serial"
_ "v2ray.com/core/main/distro/all"
v2internet "v2ray.com/core/transport/internet"
v2applog "v2ray.com/core/app/log"
v2commlog "v2ray.com/core/common/log"
)
const (
v2Assert = "v2ray.location.asset"
assetperfix = "/dev/libv2rayfs0/asset"
)
/*V2RayPoint V2Ray Point Server
This is territory of Go, so no getter and setters!
*/
type V2RayPoint struct {
SupportSet V2RayVPNServiceSupportsSet
statsManager v2stats.Manager
dialer *VPN.ProtectedDialer
status *CoreI.Status
escorter *Escort.Escorting
v2rayOP *sync.Mutex
closeChan chan struct{}
PackageName string
DomainName string
ConfigureFileContent string
EnableLocalDNS bool
ForwardIpv6 bool
}
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
type V2RayVPNServiceSupportsSet interface {
Setup(Conf string) int
Prepare() int
Shutdown() int
Protect(int) int
OnEmitStatus(int, string) int
SendFd() int
}
/*RunLoop Run V2Ray main loop
*/
func (v *V2RayPoint) RunLoop() (err error) {
v.v2rayOP.Lock()
defer v.v2rayOP.Unlock()
//Construct Context
v.status.PackageName = v.PackageName
if !v.status.IsRunning {
v.closeChan = make(chan struct{})
v.dialer.PrepareResolveChan()
go v.dialer.PrepareDomain(v.DomainName, v.closeChan)
go func() {
select {
// wait until resolved
case <-v.dialer.ResolveChan():
// shutdown VPNService if server name can not reolved
if !v.dialer.IsVServerReady() {
log.Println("vServer cannot resolved, shutdown")
v.StopLoop()
v.SupportSet.Shutdown()
}
// stop waiting if manually closed
case <-v.closeChan:
}
}()
err = v.pointloop()
}
return
}
/*StopLoop Stop V2Ray main loop
*/
func (v *V2RayPoint) StopLoop() (err error) {
v.v2rayOP.Lock()
defer v.v2rayOP.Unlock()
if v.status.IsRunning {
close(v.closeChan)
v.shutdownInit()
v.SupportSet.OnEmitStatus(0, "Closed")
}
return
}
//Delegate Funcation
func (v *V2RayPoint) GetIsRunning() bool {
return v.status.IsRunning
}
//Delegate Funcation
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
if v.statsManager == nil {
return 0
}
counter := v.statsManager.GetCounter(fmt.Sprintf("inbound>>>%s>>>traffic>>>%s", tag, direct))
if counter == nil {
return 0
}
return counter.Set(0)
}
func (v *V2RayPoint) shutdownInit() {
v.status.IsRunning = false
v.status.Vpoint.Close()
v.status.Vpoint = nil
v.statsManager = nil
v.escorter.EscortingDown()
}
func (v *V2RayPoint) pointloop() error {
if err := v.runTun2socks(); err != nil {
log.Println(err)
return err
}
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS,
v.ForwardIpv6,
v.DomainName)
log.Println("loading v2ray config")
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
if err != nil {
log.Println(err)
return err
}
log.Println("new v2ray core")
inst, err := v2core.New(config)
if err != nil {
log.Println(err)
return err
}
v.status.Vpoint = inst
v.statsManager = inst.GetFeature(v2stats.ManagerType()).(v2stats.Manager)
log.Println("start v2ray core")
v.status.IsRunning = true
if err := v.status.Vpoint.Start(); err != nil {
v.status.IsRunning = false
log.Println(err)
return err
}
v.SupportSet.Prepare()
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
v.SupportSet.OnEmitStatus(0, "Running")
return nil
}
func initV2Env() {
if os.Getenv(v2Assert) != "" {
return
}
//Initialize asset API, Since Raymond Will not let notify the asset location inside Process,
//We need to set location outside V2Ray
os.Setenv(v2Assert, assetperfix)
//Now we handle read
v2filesystem.NewFileReader = func(path string) (io.ReadCloser, error) {
if strings.HasPrefix(path, assetperfix) {
p := path[len(assetperfix)+1:]
//is it overridden?
//by, ok := overridedAssets[p]
//if ok {
// return os.Open(by)
//}
return mobasset.Open(p)
}
return os.Open(path)
}
}
//Delegate Funcation
func TestConfig(ConfigureFileContent string) error {
initV2Env()
_, err := v2serial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
return err
}
/*NewV2RayPoint new V2RayPoint*/
func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
initV2Env()
// inject our own log writer
v2applog.RegisterHandlerCreator(v2applog.LogType_Console,
func(lt v2applog.LogType,
options v2applog.HandlerCreatorOptions) (v2commlog.Handler, error) {
return v2commlog.NewLogger(createStdoutLogWriter()), nil
})
dialer := VPN.NewPreotectedDialer(s)
v2internet.UseAlternativeSystemDialer(dialer)
status := &CoreI.Status{}
return &V2RayPoint{
SupportSet: s,
v2rayOP: new(sync.Mutex),
status: status,
dialer: dialer,
escorter: &Escort.Escorting{Status: status},
}
}
func (v V2RayPoint) runTun2socks() error {
shipb := shippedBinarys.FirstRun{Status: v.status}
if err := shipb.CheckAndExport(); err != nil {
log.Println(err)
return err
}
v.escorter.EscortingUp()
go v.escorter.EscortRun(
v.status.GetApp("tun2socks"),
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
v.SupportSet.SendFd)
return nil
}
/*CheckVersion int
This func will return libv2ray binding version.
*/
func CheckVersion() int {
return CoreI.CheckVersion()
}
/*CheckVersionX string
This func will return libv2ray binding version and V2Ray version used.
*/
func CheckVersionX() string {
return fmt.Sprintf("Libv2rayLite V%d, Core V%s", CheckVersion(), v2core.Version())
}
-3
View File
@@ -1,3 +0,0 @@
package libv2ray
//go:generate make all
-1
View File
@@ -1 +0,0 @@
readme.txt
@@ -1,13 +0,0 @@
Platdep=shippedBinary.386 shippedBinary.amd64 shippedBinary.arm64 shippedBinary.arm
shippedBinaryDep:
go get -u github.com/jteeuwen/go-bindata/...
shippedBinary.%:
go-bindata -nometadata -nomemcopy -pkg shippedBinarys -o ./binary_$*.go -tags $* ArchIndep/ ArchDep/$*/
shippedBinary:shippedBinaryDep $(Platdep)
@echo "Done"
clean:
-rm binary*
@@ -1,69 +0,0 @@
package shippedBinarys
import (
"log"
"os"
"strconv"
"github.com/2dust/AndroidLibV2rayLite/CoreI"
)
type FirstRun struct {
Status *CoreI.Status
}
func (v *FirstRun) checkIfRcExist() error {
datadir := v.Status.GetDataDir()
if _, err := os.Stat(datadir + strconv.Itoa(CoreI.CheckVersion())); !os.IsNotExist(err) {
log.Println("file exists")
return nil
}
IndepDir, err := AssetDir("ArchIndep")
log.Println(IndepDir)
if err != nil {
return err
}
for _, fn := range IndepDir {
log.Println(datadir+"ArchIndep/"+fn)
err := RestoreAsset(datadir, "ArchIndep/"+fn)
log.Println(err)
//GrantPremission
os.Chmod(datadir+"ArchIndep/"+fn, 0700)
log.Println(os.Remove(datadir + fn))
log.Println(os.Symlink(datadir+"ArchIndep/"+fn, datadir + fn))
}
DepDir, err := AssetDir("ArchDep")
log.Println(DepDir)
if err != nil {
return err
}
for _, fn := range DepDir {
DepDir2, err := AssetDir("ArchDep/" + fn)
log.Println("ArchDep/" + fn)
if err != nil {
return err
}
for _, FND := range DepDir2 {
log.Println(datadir+"ArchDep/"+fn+"/"+FND)
RestoreAsset(datadir, "ArchDep/"+fn+"/"+FND)
os.Chmod(datadir+"ArchDep/"+fn+"/"+FND, 0700)
log.Println(os.Remove(datadir + FND))
log.Println(os.Symlink(datadir+"ArchDep/"+fn+"/"+FND, datadir+FND))
}
}
s, _ := os.Create(datadir + strconv.Itoa(CoreI.CheckVersion()))
s.Close()
return nil
}
func (v *FirstRun) CheckAndExport() error {
return v.checkIfRcExist()
}
-128
View File
@@ -1,128 +0,0 @@
# Copyright (C) 2009 The Android Open Source Project
#
# 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.
#
#
LOCAL_PATH := $(call my-dir)
ROOT_PATH := $(LOCAL_PATH)
########################################################
## libancillary
########################################################
include $(CLEAR_VARS)
ANCILLARY_SOURCE := fd_recv.c fd_send.c
LOCAL_MODULE := libancillary
#LOCAL_CFLAGS += -I$(LOCAL_PATH)/libancillary
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libancillary
LOCAL_SRC_FILES := $(addprefix libancillary/, $(ANCILLARY_SOURCE))
include $(BUILD_STATIC_LIBRARY)
########################################################
## tun2socks
########################################################
include $(CLEAR_VARS)
LOCAL_CFLAGS := -std=gnu99
LOCAL_CFLAGS += -DBADVPN_THREADWORK_USE_PTHREAD -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE
LOCAL_CFLAGS += -DBADVPN_USE_SIGNALFD -DBADVPN_USE_EPOLL
LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN -DBADVPN_THREAD_SAFE
LOCAL_CFLAGS += -DNDEBUG -DANDROID
LOCAL_CFLAGS += -I
LOCAL_STATIC_LIBRARIES := libancillary
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/badvpn/libancillary \
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv4 \
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv6 \
$(LOCAL_PATH)/badvpn/lwip/src/include \
$(LOCAL_PATH)/badvpn/lwip/custom \
$(LOCAL_PATH)/badvpn \
$(LOCAL_PATH)/libancillary
TUN2SOCKS_SOURCES := \
base/BLog_syslog.c \
system/BReactor_badvpn.c \
system/BSignal.c \
system/BConnection_common.c \
system/BConnection_unix.c \
system/BTime.c \
system/BUnixSignal.c \
system/BNetwork.c \
flow/StreamRecvInterface.c \
flow/PacketRecvInterface.c \
flow/PacketPassInterface.c \
flow/StreamPassInterface.c \
flow/SinglePacketBuffer.c \
flow/BufferWriter.c \
flow/PacketBuffer.c \
flow/PacketStreamSender.c \
flow/PacketPassConnector.c \
flow/PacketProtoFlow.c \
flow/PacketPassFairQueue.c \
flow/PacketProtoEncoder.c \
flow/PacketProtoDecoder.c \
socksclient/BSocksClient.c \
tuntap/BTap.c \
lwip/src/core/udp.c \
lwip/src/core/memp.c \
lwip/src/core/init.c \
lwip/src/core/pbuf.c \
lwip/src/core/tcp.c \
lwip/src/core/tcp_out.c \
lwip/src/core/netif.c \
lwip/src/core/def.c \
lwip/src/core/ip.c \
lwip/src/core/mem.c \
lwip/src/core/tcp_in.c \
lwip/src/core/stats.c \
lwip/src/core/inet_chksum.c \
lwip/src/core/timeouts.c \
lwip/src/core/ipv4/icmp.c \
lwip/src/core/ipv4/igmp.c \
lwip/src/core/ipv4/ip4_addr.c \
lwip/src/core/ipv4/ip4_frag.c \
lwip/src/core/ipv4/ip4.c \
lwip/src/core/ipv4/autoip.c \
lwip/src/core/ipv6/ethip6.c \
lwip/src/core/ipv6/inet6.c \
lwip/src/core/ipv6/ip6_addr.c \
lwip/src/core/ipv6/mld6.c \
lwip/src/core/ipv6/dhcp6.c \
lwip/src/core/ipv6/icmp6.c \
lwip/src/core/ipv6/ip6.c \
lwip/src/core/ipv6/ip6_frag.c \
lwip/src/core/ipv6/nd6.c \
lwip/custom/sys.c \
tun2socks/tun2socks.c \
base/DebugObject.c \
base/BLog.c \
base/BPending.c \
system/BDatagram_unix.c \
flowextra/PacketPassInactivityMonitor.c \
tun2socks/SocksUdpGwClient.c \
udpgw_client/UdpGwClient.c
LOCAL_MODULE := tun2socks
LOCAL_LDLIBS := -ldl -llog
LOCAL_SRC_FILES := $(addprefix badvpn/, $(TUN2SOCKS_SOURCES))
include $(BUILD_SYSTEM)/build-executable.mk
@@ -1,79 +0,0 @@
#!/bin/bash
# Thanks to https://gist.github.com/wenzhixin/43cf3ce909c24948c6e7
# Execute this script in your home directory. Lines 17 and 21 will prompt you for a y/n
# Install Oracle JDK 8
apt-get update
apt-get install -y openjdk-8-jdk
apt-get install -y unzip make expect # NDK stuff
# Get SDK tools (link from https://developer.android.com/studio/index.html#downloads)
wget -q https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
mkdir android-sdk-linux
unzip sdk*.zip -d android-sdk-linux
# Get NDK (https://developer.android.com/ndk/downloads/index.html)
# wget -q https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip
# unzip android-ndk*.zip >> /dev/null
ACCEPT_LICENSES_URL=https://gist.githubusercontent.com/xiaokangwang/1489fd223d26581bfec92adb3cb0088e/raw/328eb6925099df5aae3e76790f8232f0fc378f8b/accept-licenses
ACCEPT_LICENSES_ITEM="android-sdk-license-bcbbd656|intel-android-sysimage-license-1ea702d1|android-sdk-license-2742d1c5"
# Let it update itself and install some stuff
cd android-sdk-linux/tools
curl -L -o accept-licenses $ACCEPT_LICENSES_URL
chmod +x accept-licenses
./accept-licenses "./android update sdk --use-sdk-wrapper --all --no-ui" $ACCEPT_LICENSES_ITEM >/dev/null
# Download every build-tools version that has ever existed
# This will save you time! Thank me later for this
#./accept-licenses "./android update sdk --use-sdk-wrapper --all --no-ui --filter 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27" $ACCEPT_LICENSES_ITEM
PACKAGE_PARSE_URL=https://gist.githubusercontent.com/xiaokangwang/06268fb23034ed94bc301880e862da09/raw/afd95cbbe2f8c1d9e7b0277b7c5ef39af756a6ee/parse.awk
reduceout=https://gist.githubusercontent.com/xiaokangwang/4684bdb5c3415b943f52aa4803386480/raw/b46dab1cc60f02c0d87f88f01e27157034218faa/out.awk
cd bin
curl -L -o parse.awk $PACKAGE_PARSE_URL
curl -L -o reduce.awk $reduceout
sudo apt-get install gawk
./sdkmanager --verbose --list |awk -f parse.awk > ~/package_to_install
readarray -t filenames < $HOME/package_to_install
cat $HOME/package_to_install
yes|./sdkmanager --verbose "${filenames[@]}" |awk -f reduce.awk
# If you need additional packages for your app, check available packages with:
# ./android list sdk --all
# install certain packages with:
# ./android update sdk --no-ui --all --filter 1,2,3,<...>,N
# where N is the number of the package in the list (see previous command)
./sdkmanager "ndk-bundle"
# Add the directory containing executables in PATH so that they can be found
echo 'export ANDROID_HOME=$HOME/android-sdk-linux' >> ~/.bashrc
echo 'export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools' >> ~/.bashrc
# echo 'export NDK_HOME=$HOME/android-ndk-r15c' >> ~/.bashrc
# echo 'export ANDROID_NDK_HOME=$NDK_HOME' >> ~/.bashrc
source ~/.bashrc
# Make sure you can execute 32 bit executables if this is 64 bit machine, otherwise skip this
dpkg --add-architecture i386
apt-get update
apt-get install -y libc6:i386 libstdc++6:i386 zlib1g:i386
-32
View File
@@ -1,32 +0,0 @@
package libv2ray
// This struct creates our own log writer without datatime stamp
// As Android adds time stamps on each line
import (
"log"
"os"
v2commlog "v2ray.com/core/common/log"
)
type consoleLogWriter struct {
logger *log.Logger
}
func (w *consoleLogWriter) Write(s string) error {
w.logger.Print(s)
return nil
}
func (w *consoleLogWriter) Close() error {
return nil
}
// This logger won't print data/time stamps
func createStdoutLogWriter() v2commlog.WriterCreator {
return func() v2commlog.Writer {
return &consoleLogWriter{
logger: log.New(os.Stdout, "", 0)}
}
}
+26 -15
View File
@@ -1,29 +1,40 @@
v2rayNG 隐私条款
**v2rayNG 隐私权政策**
最后更新 2017-11-22
本政策自2023年11月17日起施行
v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。**
2dust 将 v2rayNG 应用程序构建为开源应用程序。 本服务由 2dust 免费提供,并且旨在按原样使用。
1. 信息收集
v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私权政策。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。**
v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。
**信息收集**
v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权
v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私权政策
当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。**
v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。
2. 信息共享
当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。**
我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。
**信息共享**
3. 信息存留
我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。
除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。
**信息存留**
4. 信息泄露
除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。
我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。**
**信息泄露**
5. 条款修改
我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。**
**儿童隐私**
这些服务不针对 13 岁以下的任何人。我不会故意收集 13 岁以下儿童的个人身份信息。 如果我发现 13 岁以下的儿童向我提供了个人信息,我会立即从我们的服务器中删除该信息。 如果您是父母或监护人,并且您知道您的孩子向我们提供了个人信息,请与我联系,以便我能够采取必要的行动。
**条款修改**
我们保留修改这份隐私权政策的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私权政策。**
**联系我们**
如果您对我的隐私政策有任何疑问或建议,请随时通过 CaptainIronng@protonmail.com 与我联系。
我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。**
+31
View File
@@ -1,5 +1,36 @@
# v2rayNG
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-21%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.9.23-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)
[![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn)
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" />
</a>
### Telegram Channel
[github_2dust](https://t.me/github_2dust)
### Usage
#### Geoip and Geosite
- 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)
### More in our [wiki](https://github.com/2dust/v2rayNG/wiki)
### Development guide
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
The aar can be compiled from the Golang project [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite).
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. For WSA, VPN permission need to be granted via
`appops set [package name] ACTIVATE_VPN allow`
+1
View File
@@ -7,3 +7,4 @@
/captures
*.apk
signing.properties
*.aar
-130
View File
@@ -1,130 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileOptions {
targetCompatibility = "8"
sourceCompatibility = "8"
}
defaultConfig {
applicationId "com.v2ray.ang"
minSdkVersion 17
targetSdkVersion Integer.parseInt("$targetSdkVer")
multiDexEnabled true
versionCode 212
versionName "1.0.2"
}
signingConfigs {
release {
storeFile file("../key.jks")
keyAlias 'ang'
keyPassword '123456'
storePassword '123456'
}
debug {
storeFile file("../key.jks")
keyAlias 'ang'
keyPassword '123456'
storePassword '123456'
}
}
buildTypes {
release {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.release
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.release
}
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
splits {
abi {
enable true
reset()
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
universalApk true //generate an additional APK that contains all the ABIs
}
}
// map for the version code
project.ext.versionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride =
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) *
1000000 + android.defaultConfig.versionCode
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
implementation project(':dpreference')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
// 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"
// DSL
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
implementation "org.jetbrains.anko:anko-design:$ankoVersion"
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'io.reactivex:rxjava:1.3.4'
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
implementation 'com.dinuscxj:recycleritemdecoration:1.0.0'
implementation 'io.reactivex:rxkotlin:0.60.0'
implementation 'me.dm7.barcodescanner:core:1.9.8'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'com.beust:klaxon:3.0.1'
implementation 'com.android.support:multidex:1.0.3'
implementation(name: 'libv2ray', ext: 'aar')
//implementation(name: 'tun2socks', ext: 'aar')
}
buildscript {
repositories {
google()
jcenter()
maven { url 'https://maven.google.com' }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion"
}
}
repositories {
flatDir {
dirs 'libs'
}
}
+132
View File
@@ -0,0 +1,132 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.v2ray.ang"
compileSdk = 34
defaultConfig {
applicationId = "com.v2ray.ang"
minSdk = 21
targetSdk = 34
versionCode = 593
versionName = "1.9.2"
multiDexEnabled = true
splits {
abi {
isEnable = true
include(
"arm64-v8a",
"armeabi-v7a",
"x86_64",
"x86"
)
isUniversalApk = true
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildTypes {
release {
isMinifyEnabled = false
}
debug {
isMinifyEnabled = false
}
}
sourceSets {
getByName("main") {
jniLibs.srcDirs("libs")
}
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
applicationVariants.all {
val variant = this
val versionCodes =
mapOf("armeabi-v7a" to 4, "arm64-v8a" to 4, "x86" to 4, "x86_64" to 4, "universal" to 4)
variant.outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
.forEach { output ->
val abi = if (output.getFilter("ABI") != null)
output.getFilter("ABI")
else
"universal"
output.outputFileName = "v2rayNG_${variant.versionName}_${abi}.apk"
if (versionCodes.containsKey(abi)) {
output.versionCodeOverride = (1000000 * versionCodes[abi]!!).plus(variant.versionCode)
} else {
return@forEach
}
}
}
buildFeatures {
viewBinding = true
buildConfig = true
}
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
testImplementation(libs.junit)
implementation(libs.flexbox)
// Androidx
implementation(libs.constraintlayout)
implementation(libs.legacy.support.v4)
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.cardview)
implementation(libs.preference.ktx)
implementation(libs.recyclerview)
implementation(libs.fragment.ktx)
implementation(libs.multidex)
implementation(libs.viewpager2)
// Androidx ktx
implementation(libs.activity.ktx)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.lifecycle.runtime.ktx)
//kotlin
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.mmkv.static)
implementation(libs.gson)
implementation(libs.rxjava)
implementation(libs.rxandroid)
implementation(libs.rxpermissions)
implementation(libs.toastcompat)
implementation(libs.editorkit)
implementation(libs.language.base)
implementation(libs.language.json)
implementation(libs.quickie.bundled)
implementation(libs.core)
implementation(libs.work.runtime.ktx)
implementation(libs.work.multiprocess)
}
-1
View File
@@ -1 +0,0 @@
https://github.com/2dust/v2rayNG/tree/master/AndroidLibV2rayLite
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-61
View File
@@ -1,61 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in G:\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
-dontwarn org.apache.commons.**
-keep class org.apache.commons.** { *;}
# Disable debug info output
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String,int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
static void throwUninitializedPropertyAccessException(java.lang.String);
}
-dontwarn org.jetbrains.anko.internals.**
-keep class org.jetbrains.anko.internals.** { *;}
-dontwarn rx.internal.util.unsafe.**
-keep class rx.internal.util.unsafe.** { *;}
-dontwarn app.dinus.**
-keep class app.dinus.** { *;}
-keepclassmembers class ** {
@com.hwangjr.rxbus.annotation.Subscribe public *;
@com.hwangjr.rxbus.annotation.Produce public *;
}
-keep class libv2ray.** { *;}
@@ -1,13 +0,0 @@
package com.v2ray.ang;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="app_name" type="string">v2rayNG (DEV)</item>
</resources>
+178 -52
View File
@@ -1,48 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.v2ray.ang">
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingLeanbackLauncher">
<supports-screens
android:anyDensity="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"/>
android:anyDensity="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true" />
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-sdk
android:minSdkVersion="21"
tools:overrideLibrary="com.blacksquircle.ui.editorkit" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
<!-- <useapplications-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
android:minSdkVersion="34" />
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application
android:name=".AngApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:banner="@mipmap/ic_banner"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppThemeDayNight"
android:usesCleartextTraffic="true"
tools:targetApi="m">
<activity
android:name=".ui.MainActivity"
android:theme="@style/AppTheme.NoActionBar"
android:launchMode="singleTask">
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/AppThemeDayNight.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
@@ -53,30 +77,76 @@
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:name=".ui.RoutingSettingsActivity"
android:windowSoftInputMode="stateUnchanged" />
<activity android:name=".ui.SubSettingActivity" />
android:exported="false"
android:name=".ui.ScannerActivity" />
<activity
android:exported="false"
android:name=".ui.LogcatActivity" />
<activity
android:exported="false"
android:name=".ui.RoutingSettingActivity" />
<activity
android:exported="false"
android:name=".ui.RoutingEditActivity" />
<activity
android:exported="false"
android:name=".ui.SubSettingActivity" />
<activity
android:exported="false"
android:name=".ui.UserAssetActivity" />
<activity
android:exported="false"
android:name=".ui.UserAssetUrlActivity" />
<activity android:name=".ui.SubEditActivity" />
<activity android:name=".ui.ScScannerActivity" />
<activity android:name=".ui.ScSwitchActivity" />
<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"
android:theme="@style/AppTheme.NoActionBar.Translucent" />
<activity
android:exported="true"
android:name=".ui.UrlSchemeActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="v2rayng" />
<data android:host="install-config" />
<data android:host="install-sub" />
</intent-filter>
</activity>
<activity
android:exported="false"
android:name=".ui.AboutActivity" />
<service
android:name=".service.V2RayVpnService"
@@ -84,6 +154,7 @@
android:exported="false"
android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE"
android:foregroundServiceType="specialUse"
android:process=":RunSoLibV2RayDaemon">
<intent-filter>
<action android:name="android.net.VpnService" />
@@ -91,30 +162,60 @@
<meta-data
android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
android:value="true" />
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="vpn" />
</service>
<!--<receiver android:name=".receiver.WidgetProvider">-->
<!--<meta-data-->
<!--android:name="android.appwidget.provider"-->
<!--android:resource="@xml/app_widget_provider" />-->
<!--<intent-filter>-->
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />-->
<!--<action android:name="com.v2ray.ang.action.widget.click" />-->
<!--</intent-filter>-->
<!--</receiver>-->
<service
android:name=".service.V2RayProxyOnlyService"
android:exported="false"
android:label="@string/app_name"
android:foregroundServiceType="specialUse"
android:process=":RunSoLibV2RayDaemon">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="proxy" />
</service>
<service
android:name=".service.V2RayTestService"
android:exported="false"
android:process=":RunSoLibV2RayDaemon"
/>
<receiver
android:exported="true"
android:name=".receiver.WidgetProvider"
android:process=":RunSoLibV2RayDaemon">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.v2ray.ang.action.widget.click" />
<action android:name="com.v2ray.ang.action.activity" />
</intent-filter>
</receiver>
<service
android:exported="true"
android:name=".service.QSTileService"
android:icon="@drawable/ic_v"
android:icon="@drawable/ic_stat_name"
android:label="@string/app_tile_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:process=":RunSoLibV2RayDaemon">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="tile" />
</service>
<!-- =====================Tasker===================== -->
<activity
android:exported="true"
android:name=".ui.TaskerActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
@@ -123,13 +224,38 @@
</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" />
</intent-filter>
</receiver>
<!-- =====================Tasker===================== -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.cache"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/cache_paths" />
</provider>
</application>
</manifest>
</manifest>
@@ -1,144 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.vending.billing;
import android.os.Bundle;
/**
* InAppBillingService is the service that provides in-app billing version 3 and beyond.
* This service provides the following features:
* 1. Provides a new API to get details of in-app items published for the app including
* price, type, title and description.
* 2. The purchase flow is synchronous and purchase information is available immediately
* after it completes.
* 3. Purchase information of in-app purchases is maintained within the Google Play system
* till the purchase is consumed.
* 4. An API to consume a purchase of an inapp item. All purchases of one-time
* in-app items are consumable and thereafter can be purchased again.
* 5. An API to get current purchases of the user immediately. This will not contain any
* consumed purchases.
*
* All calls will give a response code with the following possible values
* RESULT_OK = 0 - success
* RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
* RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
* RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
* RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
* RESULT_ERROR = 6 - Fatal error during the API action
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
*/
interface IInAppBillingService {
/**
* Checks support for the requested billing API version, package and in-app type.
* Minimum API version supported by this interface is 3.
* @param apiVersion the billing version which the app is using
* @param packageName the package name of the calling app
* @param type type of the in-app item being purchased "inapp" for one-time purchases
* and "subs" for subscription.
* @return RESULT_OK(0) on success, corresponding result code on failures
*/
int isBillingSupported(int apiVersion, String packageName, String type);
/**
* Provides details of a list of SKUs
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
* with a list JSON strings containing the productId, price, title and description.
* This API can be called with a maximum of 20 SKUs.
* @param apiVersion billing API version that the Third-party is using
* @param packageName the package name of the calling app
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "DETAILS_LIST" with a StringArrayList containing purchase information
* in JSON format similar to:
* '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
* "title : "Example Title", "description" : "This is an example description" }'
*/
Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
/**
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
* the type, a unique purchase token and an optional developer payload.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param sku the SKU of the in-app item as published in the developer console
* @param type the type of the in-app item ("inapp" for one-time purchases
* and "subs" for subscription).
* @param developerPayload optional argument to be sent back with the purchase information
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "BUY_INTENT" - PendingIntent to start the purchase flow
*
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
* If the purchase is successful, the result data will contain the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_DATA" - String in JSON format similar to
* '{"orderId":"12999763169054705758.1371079406387615",
* "packageName":"com.example.app",
* "productId":"exampleSku",
* "purchaseTime":1345678900000,
* "purchaseToken" : "122333444455555",
* "developerPayload":"example developer payload" }'
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
* was signed with the private key of the developer
* TODO: change this to app-specific keys.
*/
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
String developerPayload);
/**
* Returns the current SKUs owned by the user of the type and package name specified along with
* purchase information and a signature of the data to be validated.
* This will return all SKUs that have been purchased in V3 and managed items purchased using
* V1 and V2 that have not been consumed.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param type the type of the in-app items being requested
* ("inapp" for one-time purchases and "subs" for subscription).
* @param continuationToken to be set as null for the first call, if the number of owned
* skus are too many, a continuationToken is returned in the response bundle.
* This method can be called again with the continuation token to get the next set of
* owned skus.
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
* of the purchase information
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
* next set of in-app purchases. Only set if the
* user has more owned skus than the current list.
*/
Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
/**
* Consume the last purchase of the given SKU. This will result in this item being removed
* from all subsequent responses to getPurchases() and allow re-purchase of this item.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param purchaseToken token in the purchase information JSON that identifies the purchase
* to be consumed
* @return 0 if consumption succeeded. Appropriate error values for failures.
*/
int consumePurchase(int apiVersion, String packageName, String purchaseToken);
}
@@ -0,0 +1,73 @@
[
{
"remarks": "绕过bittorrent",
"outboundTag": "direct",
"protocol": [
"bittorrent"
]
},
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "阻断广告",
"outboundTag": "block",
"domain": [
"geosite:category-ads-all"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
"ip": [
"geoip:private"
]
},
{
"remarks": "代理GFW",
"outboundTag": "proxy",
"domain": [
"geosite:gfw",
"geosite:greatfire"
]
},
{
"remarks": "代理Google等",
"outboundTag": "proxy",
"ip": [
"1.0.0.1",
"1.1.1.1",
"8.8.8.8",
"8.8.4.4",
"geoip:facebook",
"geoip:fastly",
"geoip:google",
"geoip:netflix",
"geoip:telegram",
"geoip:twitter"
]
},
{
"remarks": "最终直连",
"port": "0-65535",
"outboundTag": "direct"
}
]
@@ -0,0 +1,34 @@
[
{
"remarks": "阻断udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "阻断广告",
"outboundTag": "block",
"domain": [
"geosite:category-ads-all"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
"ip": [
"geoip:private"
]
},
{
"remarks": "最终代理",
"port": "0-65535",
"outboundTag": "proxy"
}
]
@@ -0,0 +1,81 @@
[
{
"remarks": "Google cn",
"outboundTag": "proxy",
"domain": [
"domain:googleapis.cn",
"domain:gstatic.com"
]
},
{
"remarks": "阻断udp443",
"outboundTag": "block",
"port": "443",
"network": "udp"
},
{
"remarks": "阻断广告",
"outboundTag": "block",
"domain": [
"geosite:category-ads-all"
]
},
{
"remarks": "绕过局域网域名",
"outboundTag": "direct",
"domain": [
"geosite:private"
]
},
{
"remarks": "绕过局域网IP",
"outboundTag": "direct",
"ip": [
"geoip:private"
]
},
{
"remarks": "绕过中国域名",
"outboundTag": "direct",
"domain": [
"domain:dns.alidns.com",
"domain:doh.pub",
"domain:dot.pub",
"domain:doh.360.cn",
"domain:dot.360.cn",
"geosite:cn",
"geosite:geolocation-cn"
]
},
{
"remarks": "绕过中国IP",
"outboundTag": "direct",
"ip": [
"223.5.5.5/32",
"223.6.6.6/32",
"2400:3200::1/128",
"2400:3200:baba::1/128",
"119.29.29.29/32",
"1.12.12.12/32",
"120.53.53.53/32",
"2402:4e00::/128",
"2402:4e00:1::/128",
"180.76.76.76/32",
"2400:da00::6666/128",
"114.114.114.114/32",
"114.114.115.115/32",
"180.184.1.1/32",
"180.184.2.2/32",
"101.226.4.6/32",
"218.30.118.6/32",
"123.125.81.6/32",
"140.207.198.6/32",
"geoip:cn"
]
},
{
"remarks": "最终代理",
"port": "0-65535",
"outboundTag": "proxy"
}
]
+166 -121
View File
@@ -1,196 +1,241 @@
com.android.chrome
com.google.android.googlequicksearchbox
com.google.android.apps.photos
com.google.android.youtube
com.google.android.gm
com.google.android.apps.plus
com.android.vending
com.google.android.inputmethod.latin
com.google.android.apps.paidtasks
com.google.android.keep
com.google.android.gms.setup
com. google.android. apps.magazines
com.google.android.videos
com. google.android.gms
com.google.android.apps.books
com.google.android.music
com.google.android.play.games
com.google.android.gsf
com.google.android.gsf.login
com.app.pornhub
com.spotify.music
org.thunderdog.challegram
com.tumblr
com.twitter.android
com.xda.labs
com.kapp.youtube.final
com.google.android.ims
com.wire
mark.via.gp
com.downloader.video.tumblr
com.sololearn
com.cygames.shadowverse
com.felixfilip.scpae
amanita_design.samorost3.gp
com.devolver.reigns2
com.utopia.pxview
ch.protonmail.android
com.perol.asdpl.pixivez
com.pinterest
com.paypal.android.p2pmobile
com.arthurivanets.owly
com.rubenmayayo.reddit
com.rayark.cytus2
com.rayark.pluto
com.rayark.implosion
com.fireproofstudios.theroom4
com.netflix.mediaclient
com.instagram.android
com.google.android.apps.hangoutsdialer
com.google.android.talk
com.google.android.apps.plus
com.google.android.apps.pdfviewer
com.google.android.apps.magazines
com.google.android.apps.nbu.files
com.evernote
net.tsapps.appsales
com.google.android.apps.translate
com.google.ar.lens
com.google.android.apps.adm
com.google.android.apps.googleassistant
tw.com.gamer.android.activecenter
org.telegram.plus
com.brave.browser
com.breel.wallpapers18
com.teslacoilsw.launcher
com.lastpass.lpandroid
org.kustom.widget
com.fooview.android.fooview
com.google.android.apps.docs
com.google.android.apps.maps
com.facebook.services
com.facebook.system
com.facebook.katana
com.nianticlabs.ingress.prime.qa
com.vanced.android.youtube
com.nianticproject.ingress
com.quoord.tapatalkpro.activity
org.mozilla.firefox
com.reddit.frontpage
com.google.android.apps.fitness
android
au.com.shiftyjelly.pocketcasts
com.google.android.gms
com.android.providers.telephony
com.resilio.sync
com.google.android.apps.googlevoice
com.discord
com.cradle.iitc_mobile
bbc.mobile.news.ww
be.mygod.vpnhotspot
ch.protonmail.android
co.wanqu.android
com.alphainventor.filemanager
com.amazon.kindle
com.amazon.mshop.android.shopping
com.android.chrome
com.android.providers.downloads
com.android.providers.downloads.ui
com.android.providers.telephony
com.android.settings
com.android.vending
com.android6park.m6park
com.apkpure.aegon
com.apkupdater
com.app.pornhub
com.arthurivanets.owly
com.asahi.tida.tablet
com.authy.authy
com.avmovie
com.ballistiq.artstation
com.binance.dev
com.bitly.app
com.brave.browser
com.brave.browser_beta
com.breel.wallpapers18
com.bvanced.android.youtube
com.chrome.beta
com.chrome.canary
com.chrome.dev
com.cl.newt66y
com.cradle.iitc_mobile
com.cygames.shadowverse
com.devhd.feedly
com.devolver.reigns2
com.discord
com.downloader.video.tumblr
com.driverbrowser
com.dropbox.android
com.duolingo
com.duckduckgo.mobile.android
com.dv.adm
com.estrongs.android.pop
com.estrongs.android.pop.pro
com.evernote
com.facebook.katana
com.facebook.lite
com.facebook.mlite
com.facebook.orca
com.facebook.services
com.facebook.system
com.fastaccess.github
com.felixfilip.scpae
com.fireproofstudios.theroom4
com.firstrowria.pushnotificationtester
com.flyersoft.moonreaderp
com.fooview.android.fooview
com.fvd.eversync
com.gameloft.android.anmp.glofta8hm
com.gameloft.android.anmp.glofta9hm
com.gianlu.aria2app
com.github.yeriomin.yalpstore
com.google.android.apps.adm
com.google.android.apps.books
com.google.android.apps.docs
com.google.android.apps.docs.editors.sheets
com.google.android.apps.fitness
com.google.android.apps.googleassistant
com.google.android.apps.googlevoice
com.google.android.apps.hangoutsdialer
com.google.android.apps.inbox
com.google.android.apps.magazines
com.google.android.apps.maps
com.google.android.apps.nbu.files
com.google.android.apps.paidtasks
com.google.android.apps.pdfviewer
com.google.android.apps.photos
com.google.android.apps.plus
com.google.android.apps.translate
com.google.android.gm
com.google.android.gms
com.google.android.gms.setup
com.google.android.googlequicksearchbox
com.google.android.gsf
com.google.android.gsf.login
com.google.android.ims
com.google.android.inputmethod.latin
com.google.android.instantapps.supervisor
com.google.android.keep
com.google.android.music
com.google.android.ogyoutube
com.google.android.partnersetup
com.google.android.play.games
com.google.android.street
com.google.android.syncadapters.calendar
com.google.android.syncadapters.contacts
com.google.android.talk
com.google.android.tts
com.google.android.videos
com.google.android.youtube
com.google.ar.lens
com.hochan.coldsoup
com.ifttt.ifttt
com.imgur.mobile
com.innologica.inoreader
com.instagram.android
com.instapaper.android
com.jarvanh.vpntether
com.kapp.youtube.final
com.klinker.android.twitter_l
com.lastpass.lpandroid
com.linecorp.linelite
com.lingodeer
com.mediapods.tumbpods
com.mgoogle.android.gms
com.microsoft.emmx
com.microsoft.office.powerpoint
com.microsoft.skydrive
com.mixplorer
com.msd.consumerchinese
com.msd.professionalchinese
com.mss2011c.sharehelper
com.netflix.mediaclient
com.newin.nplayer.pro
com.nianticlabs.ingress.prime.qa
com.nianticproject.ingress
com.ninefolders.hd3
com.ninegag.android.app
com.nintendo.zara
com.nytimes.cn
com.oasisfeng.island
com.ocnt.liveapp.hw
com.orekie.search
com.patreon.android
com.paypal.android.p2pmobile
com.perol.asdpl.pixivez
com.pinterest
com.popularapp.periodcalendar
com.popularapp.videodownloaderforinstagram
com.pushbullet.android
com.quoord.tapatalkpro.activity
com.quora.android
com.rayark.cytus2
com.rayark.implosion
com.rayark.pluto
com.reddit.frontpage
com.resilio.sync
com.rhmsoft.edit
com.rubenmayayo.reddit
com.sec.android.app.sbrowser
com.sec.android.app.sbrowser.beta
com.shanga.walli
com.simplehabit.simplehabitapp
com.slack
com.snaptube.premium
com.sololearn
com.sonelli.juicessh
com.spotify.music
com.tencent.huatuo
com.termux
com.teslacoilsw.launcher
com.theinitium.news
com.thomsonreuters.reuters
com.thunkable.android.hritvik00.freenom
com.topjohnwu.magisk
com.tripadvisor.tripadvisor
com.tumblr
com.twitter.android
com.u91porn
com.u9porn
com.ubisoft.dance.justdance2015companion
com.utopia.pxview
com.valvesoftware.android.steam.communimunity
com.valvesoftware.android.steam.community
com.vanced.android.youtube
com.vimeo.android.videoapp
com.vivaldi.browser
com.vivaldi.browser.snapshot
com.vkontakte.android
com.whatsapp
com.wire
com.wuxiangai.refactor
com.xda.labs
com.xvideos.app
com.yandex.browser
com.yandex.browser.beta
com.yandex.browser.alpha
com.z28j.feel
con.medium.reader
de.apkgrabber
de.robv.android.xposed.installer
dk.tacit.android.foldersync.full
es.rafalense.telegram.themes
es.rafalense.themes
flipboard.app
fm.moon.app
fr.gouv.etalab.mastodon
github.tornaco.xposedmoduletest
idm.internet.download.manager
idm.internet.download.manager.plus
io.github.javiewer
io.github.skyhacker2.magnetsearch
io.va.exposed
it.mvilla.android.fenix2
jp.bokete.app.android
jp.naver.line.android
jp.pxv.android
luo.speedometergpspro
mark.via.gp
me.tshine.easymark
net.teeha.android.url_shortener
net.tsapps.appsales
onion.fire
org.fdroid.fdroid
org.freedownloadmanager.fdm
org.kustom.widget
org.mozilla.fennec_aurora
org.mozilla.fenix
org.mozilla.fenix.nightly
org.mozilla.firefox
org.mozilla.firefox_beta
org.mozilla.focus
org.schabi.newpipe
org.telegram.messenger
org.telegram.multi
org.telegram.plus
org.thunderdog.challegram
org.torproject.android
org.torproject.torbrowser_alpha
org.wikipedia
org.xbmc.kodi
pl.zdunex25.updater
videodownloader.downloadvideo.downloader
com.quora.android
com.lingodeer
org.wikipedia
com.ninegag.android.app
com.duolingo
com.patreon.android
com.valvesoftware.android.steam.communimunity
co.wanqu.android
jp.bokete.app.android
com.vkontakte.android
com.amazon.mshop.android.shopping
com.ubisoft.dance.justdance2015companion
com.gameloft.android.anmp.glofta8hm
com.gameloft.android.anmp.glofta9hm
com.binance.dev
com.asahi.tida.tablet
com.theinitium.news
com.driverbrowser
com.thomsonreuters.reuters
com.nytimes.cn
com.android.providers.downloads.ui
com.avmovie
bbc.mobile.news.ww
org.mozilla.focus
io.github.javiewer
com.sonelli.juicessh
con.medium.reader
com.microsoft.skydrive
com.valvesoftware.android.steam.community
com.nintendo.zara
org.torproject.torbrowser_alpha
tv.twitch.android.app
com.shanga.walli
com.whatsapp
com.wire
com.simplehabit.simplehabitapp
tw.com.gamer.android.activecenter
videodownloader.downloadvideo.downloader
uk.co.bbc.learningenglish
com.ted.android
@@ -13,8 +13,8 @@
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"inbounds": [{
@@ -54,7 +54,7 @@
"users": [
{
"id": "a3482e88-686a-4a58-8126-99c9df64b7bf",
"alterId": 64,
"alterId": 0,
"security": "auto",
"level": 8
}
@@ -81,7 +81,9 @@
},
{
"protocol": "freedom",
"settings": {},
"settings": {
"domainStrategy": "UseIP"
},
"tag": "direct"
},
{
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 12 KiB

@@ -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.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
/**
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
@@ -36,13 +36,14 @@ public interface ItemTouchHelperAdapter {
* @param fromPosition The start position of the moved item.
* @param toPosition Then resolved position of the moved item.
* @return True if the item was moved to the new adapter position.
*
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
* @see RecyclerView.ViewHolder#getAdapterPosition()
*/
boolean onItemMove(int fromPosition, int toPosition);
void onItemMoveCompleted();
/**
* Called when an item has been dismissed by a swipe.<br/>
* <br/>
@@ -50,7 +51,6 @@ public interface ItemTouchHelperAdapter {
* adjusting the underlying data to reflect this removal.
*
* @param position The position of the item dismissed.
*
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
* @see RecyclerView.ViewHolder#getAdapterPosition()
*/
@@ -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
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2015 Paul Burke
*
* 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.helper;
import android.support.v7.widget.RecyclerView;
/**
* Listener for manual initiation of a drag.
*/
public interface OnStartDragListener {
/**
* Called when a view is requesting a start of a drag.
*
* @param viewHolder The holder of the view to drag.
*/
void onStartDrag(RecyclerView.ViewHolder viewHolder);
}
@@ -17,9 +17,12 @@
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.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.jetbrains.annotations.NotNull;
/**
* An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
@@ -52,7 +55,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 +69,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,9 +113,11 @@ 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();
viewHolder.itemView.setAlpha(ALPHA_FULL);
if (viewHolder instanceof ItemTouchHelperViewHolder) {
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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; }
}
@@ -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);
}
}
@@ -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(); }
}
@@ -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);
}
}
@@ -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;
}
}
}
@@ -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; }
}
@@ -1,116 +0,0 @@
package com.v2ray.ang.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
/**
* 描述:解析二维码图片
*/
public class QRCodeDecoder {
public static final Map<DecodeHintType, Object> HINTS = new EnumMap<>(DecodeHintType.class);
static {
List<BarcodeFormat> allFormats = new ArrayList<>();
allFormats.add(BarcodeFormat.AZTEC);
allFormats.add(BarcodeFormat.CODABAR);
allFormats.add(BarcodeFormat.CODE_39);
allFormats.add(BarcodeFormat.CODE_93);
allFormats.add(BarcodeFormat.CODE_128);
allFormats.add(BarcodeFormat.DATA_MATRIX);
allFormats.add(BarcodeFormat.EAN_8);
allFormats.add(BarcodeFormat.EAN_13);
allFormats.add(BarcodeFormat.ITF);
allFormats.add(BarcodeFormat.MAXICODE);
allFormats.add(BarcodeFormat.PDF_417);
allFormats.add(BarcodeFormat.QR_CODE);
allFormats.add(BarcodeFormat.RSS_14);
allFormats.add(BarcodeFormat.RSS_EXPANDED);
allFormats.add(BarcodeFormat.UPC_A);
allFormats.add(BarcodeFormat.UPC_E);
allFormats.add(BarcodeFormat.UPC_EAN_EXTENSION);
HINTS.put(DecodeHintType.TRY_HARDER, BarcodeFormat.QR_CODE);
HINTS.put(DecodeHintType.POSSIBLE_FORMATS, allFormats);
HINTS.put(DecodeHintType.CHARACTER_SET, "utf-8");
}
private QRCodeDecoder() {
}
/**
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
*
* @param picturePath 要解析的二维码图片本地路径
* @return 返回二维码图片里的内容 或 null
*/
public static String syncDecodeQRCode(String picturePath) {
return syncDecodeQRCode(getDecodeAbleBitmap(picturePath));
}
/**
* 同步解析bitmap二维码。该方法是耗时操作,请在子线程中调用。
*
* @param bitmap 要解析的二维码图片
* @return 返回二维码图片里的内容 或 null
*/
public static String syncDecodeQRCode(Bitmap bitmap) {
Result result = null;
RGBLuminanceSource source = null;
try {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
source = new RGBLuminanceSource(width, height, pixels);
result = new MultiFormatReader().decode(new BinaryBitmap(new HybridBinarizer(source)), HINTS);
return result.getText();
} catch (Exception e) {
e.printStackTrace();
if (source != null) {
try {
result = new MultiFormatReader().decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)), HINTS);
return result.getText();
} catch (Throwable e2) {
e2.printStackTrace();
}
}
return null;
}
}
/**
* 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。感谢 https://github.com/devilsen 提的 PR
*
* @param picturePath 本地图片文件路径
* @return
*/
private static Bitmap getDecodeAbleBitmap(String picturePath) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picturePath, options);
int sampleSize = options.outHeight / 400;
if (sampleSize <= 0) {
sampleSize = 1;
}
options.inSampleSize = sampleSize;
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(picturePath, options);
} catch (Exception e) {
return null;
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -1,32 +1,45 @@
package com.v2ray.ang
//import com.squareup.leakcanary.LeakCanary
import android.support.multidex.MultiDexApplication
import com.v2ray.ang.util.AngConfigManager
import me.dozen.dpreference.DPreference
import org.jetbrains.anko.defaultSharedPreferences
import android.content.Context
import androidx.multidex.MultiDexApplication
import androidx.work.Configuration
import androidx.work.WorkManager
import com.tencent.mmkv.MMKV
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils
class AngApplication : MultiDexApplication() {
companion object {
const val PREF_LAST_VERSION = "pref_last_version"
//const val PREF_LAST_VERSION = "pref_last_version"
lateinit var application: AngApplication
}
var curIndex = -1 //Current proxy that is opened. (Used to implement restart feature)
var firstRun = false
private set
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
application = this
}
val defaultDPreference by lazy { DPreference(this, packageName + "_preferences") }
private val workManagerConfiguration: Configuration = Configuration.Builder()
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
.build()
override fun onCreate() {
super.onCreate()
// LeakCanary.install(this)
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
if (firstRun)
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
// val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
// firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
// if (firstRun)
// defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
AngConfigManager.inject(this)
MMKV.initialize(this)
Utils.setNightMode(application)
// Initialize WorkManager with the custom configuration
WorkManager.initialize(this, workManagerConfiguration)
SettingsManager.initRoutingRulesets(this)
}
}
@@ -1,45 +1,120 @@
package com.v2ray.ang
/**
*
* App Config Const
*/
object AppConfig {
const val ANG_PACKAGE = "com.v2ray.ang"
/** The application's package name. */
const val ANG_PACKAGE = BuildConfig.APPLICATION_ID
/** Directory names used in the app's file system. */
const val DIR_ASSETS = "assets"
const val DIR_BACKUPS = "backups"
/** Legacy configuration keys. */
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_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
const val VMESS_PROTOCOL: String = "vmess://"
const val SS_PROTOCOL: String = "ss://"
const val SOCKS_PROTOCOL: String = "socks://"
/** Preferences mapped to MMKV storage. */
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
const val PREF_ROUTE_ONLY_ENABLED = "pref_route_only_enabled"
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_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_RULESET = "pref_routing_ruleset"
const val PREF_MUX_ENABLED = "pref_mux_enabled"
const val PREF_MUX_CONCURRENCY = "pref_mux_concurrency"
const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurrency"
const val PREF_MUX_XUDP_QUIC = "pref_mux_xudp_quic"
const val PREF_FRAGMENT_ENABLED = "pref_fragment_enabled"
const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets"
const val PREF_FRAGMENT_LENGTH = "pref_fragment_length"
const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval"
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
const val SUBSCRIPTION_DEFAULT_UPDATE_INTERVAL = "1440" // Default is 24 hours
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
const val PREF_START_SCAN_IMMEDIATE = "pref_start_scan_immediate"
const val PREF_LANGUAGE = "pref_language"
const val PREF_UI_MODE_NIGHT = "pref_ui_mode_night"
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
const val PREF_SOCKS_PORT = "pref_socks_port"
const val PREF_HTTP_PORT = "pref_http_port"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
const val PREF_DELAY_TEST_URL = "pref_delay_test_url"
const val PREF_LOGLEVEL = "pref_core_loglevel"
const val PREF_MODE = "pref_mode"
/** Cache keys. */
const val CACHE_SUBSCRIPTION_ID = "cache_subscription_id"
const val CACHE_KEYWORD_FILTER = "cache_keyword_filter"
/** Protocol identifiers. */
const val PROTOCOL_HTTP: String = "http://"
const val PROTOCOL_HTTPS: String = "https://"
const val PROTOCOL_FREEDOM: String = "freedom"
/** Broadcast actions. */
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"
/** Tasker extras. */
const val TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
const val TASKER_EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"
const val TASKER_EXTRA_BUNDLE_SWITCH = "tasker_extra_bundle_switch"
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"
/** Tags for different proxy modes. */
const val TAG_PROXY = "proxy"
const val TAG_DIRECT = "direct"
const val TAG_BLOCKED = "block"
const val TAG_FRAGMENT = "fragment"
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
const val promotionUrl = "https://1.2345345.xyz/ads.html"
/** Network-related constants. */
const val UPLINK = "uplink"
const val DOWNLINK = "downlink"
const val DNS_AGENT = "1.1.1.1"
/** URLs for various resources. */
const val androidpackagenamelistUrl =
"https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayCustomRoutingListUrl =
"https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGUrl = "https://github.com/2dust/v2rayNG"
const val v2rayNGIssues = "$v2rayNGUrl/issues"
const val v2rayNGWikiMode = "$v2rayNGUrl/wiki/Mode"
const val v2rayNGPrivacyPolicy = "https://raw.githubusercontent.com/2dust/v2rayNG/master/CR.md"
const val PromotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
const val GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/"
const val TgChannelUrl = "https://t.me/github_2dust"
const val DelayTestUrl = "https://www.gstatic.com/generate_204"
const val DelayTestUrl2 = "https://www.google.com/generate_204"
/** DNS server addresses. */
const val DNS_PROXY = "1.1.1.1"
const val DNS_DIRECT = "223.5.5.5"
const val DNS_VPN = "1.1.1.1"
/** Ports and addresses for various services. */
const val PORT_LOCAL_DNS = "10853"
const val PORT_SOCKS = "10808"
const val PORT_HTTP = "10809"
const val WIREGUARD_LOCAL_ADDRESS_V4 = "172.16.0.2/32"
const val WIREGUARD_LOCAL_ADDRESS_V6 = "2606:4700:110:8f81:d551:a0:532e:a2b3/128"
const val WIREGUARD_LOCAL_MTU = "1420"
/** Message constants for communication. */
const val MSG_REGISTER_CLIENT = 1
const val MSG_STATE_RUNNING = 11
const val MSG_STATE_NOT_RUNNING = 12
@@ -50,12 +125,25 @@ object AppConfig {
const val MSG_STATE_STOP = 4
const val MSG_STATE_STOP_SUCCESS = 41
const val MSG_STATE_RESTART = 5
const val MSG_MEASURE_DELAY = 6
const val MSG_MEASURE_DELAY_SUCCESS = 61
const val MSG_MEASURE_CONFIG = 7
const val MSG_MEASURE_CONFIG_SUCCESS = 71
const val MSG_MEASURE_CONFIG_CANCEL = 72
object EConfigType {
val Vmess = 1
val Custom = 2
val Shadowsocks = 3
val Socks = 4
}
/** Notification channel IDs and names. */
const val RAY_NG_CHANNEL_ID = "RAY_NG_M_CH_ID"
const val RAY_NG_CHANNEL_NAME = "v2rayNG Background Service"
const val SUBSCRIPTION_UPDATE_CHANNEL = "subscription_update_channel"
const val SUBSCRIPTION_UPDATE_CHANNEL_NAME = "Subscription Update Service"
}
/** Protocols Scheme **/
const val VMESS = "vmess://"
const val CUSTOM = ""
const val SHADOWSOCKS = "ss://"
const val SOCKS = "socks://"
const val HTTP = "http://"
const val VLESS = "vless://"
const val TROJAN = "trojan://"
const val WIREGUARD = "wireguard://"
}
@@ -1,28 +0,0 @@
package com.v2ray.ang.dto
data class AngConfig(
var index: Int,
var vmess: ArrayList<VmessBean>,
var subItem: ArrayList<SubItemBean>
) {
data class VmessBean(var guid: String = "123456",
var address: String = "v2ray.cool",
var port: Int = 10086,
var id: String = "a3482e88-686a-4a58-8126-99c9df64b7bf",
var alterId: Int = 64,
var security: String = "aes-128-cfb",
var network: String = "tcp",
var remarks: String = "def",
var headerType: String = "",
var requestHost: String = "",
var path: String = "",
var streamSecurity: String = "",
var configType: Int = 1,
var configVersion: Int = 1,
var testResult: String = "",
var subid: String = "")
data class SubItemBean(var id: String = "",
var remarks: String = "",
var url: String = "")
}
@@ -2,8 +2,10 @@ package com.v2ray.ang.dto
import android.graphics.drawable.Drawable
data class AppInfo(val appName: String,
val packageName: String,
val appIcon: Drawable,
val isSystemApp: Boolean,
var isSelected: Int)
data class AppInfo(
val appName: String,
val packageName: String,
val appIcon: Drawable,
val isSystemApp: Boolean,
var isSelected: Int
)
@@ -0,0 +1,8 @@
package com.v2ray.ang.dto
data class AssetUrlItem(
var remarks: String = "",
var url: String = "",
val addedTime: Long = System.currentTimeMillis(),
var lastUpdated: Long = -1
)
@@ -0,0 +1,19 @@
package com.v2ray.ang.dto
import com.v2ray.ang.AppConfig
enum class EConfigType(val value: Int, val protocolScheme: String) {
VMESS(1, AppConfig.VMESS),
CUSTOM(2, AppConfig.CUSTOM),
SHADOWSOCKS(3, AppConfig.SHADOWSOCKS),
SOCKS(4, AppConfig.SOCKS),
VLESS(5, AppConfig.VLESS),
TROJAN(6, AppConfig.TROJAN),
WIREGUARD(7, AppConfig.WIREGUARD),
HTTP(10, AppConfig.HTTP);
companion object {
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
}
}
@@ -0,0 +1,9 @@
package com.v2ray.ang.dto
data class ProfileItem(
val configType: EConfigType,
var subscriptionId: String = "",
var remarks: String = "",
var server: String?,
var serverPort: Int?,
)
@@ -0,0 +1,12 @@
package com.v2ray.ang.dto
data class RulesetItem(
var remarks: String? = "",
var ip: List<String>? = null,
var domain: List<String>? = null,
var outboundTag: String = "",
var port: String? = null,
var network: String? = null,
var protocol: List<String>? = null,
var enabled: Boolean = true,
)
@@ -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"
}
}
@@ -0,0 +1,84 @@
package com.v2ray.ang.dto
import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.AppConfig.TAG_PROXY
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.HTTP,
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()
)
)
EConfigType.WIREGUARD ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.lowercase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean(
secretKey = "",
peers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean())
)
)
)
}
}
}
fun getProxyOutbound(): V2rayConfig.OutboundBean? {
if (configType != EConfigType.CUSTOM) {
return outboundBean
}
return fullConfig?.getProxyOutbound()
}
fun getAllOutboundTags(): MutableList<String> {
if (configType != EConfigType.CUSTOM) {
return mutableListOf(TAG_PROXY, TAG_DIRECT, TAG_BLOCKED)
}
fullConfig?.let { config ->
return config.outbounds.map { it.tag }.toMutableList()
}
return mutableListOf()
}
}
@@ -0,0 +1,6 @@
package com.v2ray.ang.dto
data class ServersCache(
val guid: String,
val profile: ProfileItem
)
@@ -0,0 +1,14 @@
package com.v2ray.ang.dto
data class SubscriptionItem(
var remarks: String = "",
var url: String = "",
var enabled: Boolean = true,
val addedTime: Long = System.currentTimeMillis(),
var lastUpdated: Long = -1,
var autoUpdate: Boolean = false,
val updateInterval: Int? = null,
var prevProfile: String? = null,
var nextProfile: String? = null,
)
@@ -1,142 +1,658 @@
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 com.v2ray.ang.util.Utils
import java.lang.reflect.Type
data class V2rayConfig(
val stats: Any?=null,
val log: LogBean,
val policy: PolicyBean,
val inbounds: ArrayList<InboundBean>,
var outbounds: ArrayList<OutboundBean>,
var dns: DnsBean,
val routing: RoutingBean) {
var remarks: String? = null,
var stats: Any? = null,
val log: LogBean,
var policy: PolicyBean?,
val inbounds: ArrayList<InboundBean>,
var outbounds: ArrayList<OutboundBean>,
var dns: DnsBean,
val routing: RoutingBean,
val api: Any? = null,
val transport: Any? = null,
val reverse: Any? = null,
var fakedns: Any? = null,
val browserForwarder: Any? = null,
var observatory: Any? = null,
var burstObservatory: Any? = null
) {
companion object {
const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8
const val DEFAULT_NETWORK = "tcp"
data class LogBean(val access: String,
val error: String,
val loglevel: String)
data class InboundBean(
var tag: String,
var port: Int,
var protocol: String,
var listen: String?=null,
val settings: InSettingsBean,
val sniffing: SniffingBean?) {
data class InSettingsBean(val auth: String? = null,
val udp: Boolean? = 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>)
const val TLS = "tls"
const val REALITY = "reality"
const val HTTP = "http"
}
data class OutboundBean(val tag: String,
var protocol: String,
var settings: OutSettingsBean?,
var streamSettings: StreamSettingsBean?,
var mux: MuxBean?) {
data class LogBean(
val access: String,
val error: String,
var loglevel: String?,
val dnsLog: Boolean? = null
)
data class OutSettingsBean(var vnext: List<VnextBean>?,
var servers: List<ServersBean>?,
var response: Response) {
data class InboundBean(
var tag: String,
var port: Int,
var protocol: String,
var listen: String? = null,
val settings: Any? = null,
val sniffing: SniffingBean?,
val streamSettings: Any? = null,
val allocate: Any? = null
) {
data class VnextBean(var address: String,
var port: Int,
var users: List<UsersBean>) {
data class InSettingsBean(
val auth: String? = null,
val udp: Boolean? = null,
val userLevel: Int? = null,
val address: String? = null,
val port: Int? = null,
val network: String? = null
)
data class UsersBean(var id: String,
var alterId: Int,
var security: String,
var level: Int)
}
data class SniffingBean(
var enabled: Boolean,
val destOverride: ArrayList<String>,
val metadataOnly: Boolean? = null,
var routeOnly: Boolean? = null
)
}
data class ServersBean(var address: String,
var method: String,
var ota: Boolean,
var password: String,
var port: Int,
var level: Int)
data class OutboundBean(
var tag: String = "proxy",
var protocol: String,
var settings: OutSettingsBean? = null,
var streamSettings: StreamSettingsBean? = null,
val proxySettings: Any? = null,
val sendThrough: String? = null,
var mux: MuxBean? = MuxBean(false)
) {
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 OutSettingsBean(
var vnext: List<VnextBean>? = null,
var fragment: FragmentBean? = null,
var noises: List<NoiseBean>? = null,
var servers: List<ServersBean>? = null,
/*Blackhole*/
var response: Response? = null,
/*DNS*/
val network: String? = null,
var address: Any? = null,
val port: Int? = null,
/*Freedom*/
var domainStrategy: String? = null,
val redirect: String? = null,
val userLevel: Int? = null,
/*Loopback*/
val inboundTag: String? = null,
/*Wireguard*/
var secretKey: String? = null,
val peers: List<WireGuardBean>? = null,
var reserved: List<Int>? = null,
var mtu: Int? = null
) {
data class TcpsettingsBean(var connectionReuse: Boolean = true,
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none",
var request: Any? = null,
var response: Any? = null)
data class VnextBean(
var address: String = "",
var port: Int = DEFAULT_PORT,
var users: List<UsersBean>
) {
data class UsersBean(
var id: String = "",
var alterId: Int? = null,
var security: String = DEFAULT_SECURITY,
var level: Int = DEFAULT_LEVEL,
var encryption: String = "",
var flow: String = ""
)
}
data class KcpsettingsBean(var mtu: Int = 1350,
var tti: Int = 20,
var uplinkCapacity: Int = 12,
var downlinkCapacity: Int = 100,
var congestion: Boolean = false,
var readBufferSize: Int = 1,
var writeBufferSize: Int = 1,
var header: HeaderBean = HeaderBean()) {
data class FragmentBean(
var packets: String? = null,
var length: String? = null,
var interval: String? = null
)
data class NoiseBean(
var type: String? = null,
var packet: String? = null,
var delay: String? = null
)
data class ServersBean(
var address: String = "",
var method: String? = null,
var ota: Boolean = false,
var password: String? = null,
var port: Int = DEFAULT_PORT,
var level: Int = DEFAULT_LEVEL,
val email: String? = null,
var 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 WireGuardBean(
var publicKey: String = "",
var endpoint: String = ""
)
}
data class StreamSettingsBean(
var network: String = DEFAULT_NETWORK,
var security: String = "",
var tcpSettings: TcpSettingsBean? = null,
var kcpSettings: KcpSettingsBean? = null,
var wsSettings: WsSettingsBean? = null,
var httpupgradeSettings: HttpupgradeSettingsBean? = null,
var splithttpSettings: SplithttpSettingsBean? = null,
var httpSettings: HttpSettingsBean? = null,
var tlsSettings: TlsSettingsBean? = null,
var quicSettings: QuicSettingBean? = null,
var realitySettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null,
val dsSettings: Any? = null,
var sockopt: SockoptBean? = null
) {
data class TcpSettingsBean(
var header: HeaderBean = HeaderBean(),
val acceptProxyProtocol: Boolean? = null
) {
data class HeaderBean(
var type: String = "none",
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 = 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 seed: String? = null
) {
data class HeaderBean(var type: String = "none")
}
data class WssettingsBean(var connectionReuse: Boolean = true,
var path: String = "",
var headers: HeadersBean = HeadersBean()) {
data class WsSettingsBean(
var path: String = "",
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<String>(), var path: String = "")
data class HttpupgradeSettingsBean(
var path: String = "",
var host: String = "",
val acceptProxyProtocol: Boolean? = null
)
data class TlssettingsBean(var allowInsecure: Boolean = true,
var serverName: String = "")
data class SplithttpSettingsBean(
var path: String = "",
var host: String = "",
val maxUploadSize: Int? = null,
val maxConcurrentUploads: Int? = null
)
data class QuicsettingBean(var security: String = "none",
var key: String = "",
var header: HeaderBean = HeaderBean()) {
data class HttpSettingsBean(
var host: List<String> = ArrayList(),
var path: String = ""
)
data class SockoptBean(
var TcpNoDelay: Boolean? = null,
var tcpKeepAliveIdle: Int? = null,
var tcpFastOpen: Boolean? = null,
var tproxy: String? = null,
var mark: Int? = null,
var dialerProxy: String? = null
)
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,
// REALITY settings
val show: Boolean = false,
var publicKey: String? = null,
var shortId: String? = null,
var spiderX: String? = 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 authority: String? = null,
var multiMode: Boolean? = null,
var idle_timeout: Int? = null,
var health_check_timeout: Int? = null
)
fun populateTransportSettings(
transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?,
authority: 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.orEmpty()).split(",").map { it.trim() }.filter { it.isNotEmpty() }
requestObj.path = (path.orEmpty()).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.orEmpty()
}
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.orEmpty()
sni = wssetting.headers.Host
wssetting.path = path ?: "/"
wsSettings = wssetting
}
"httpupgrade" -> {
val httpupgradeSetting = HttpupgradeSettingsBean()
httpupgradeSetting.host = host.orEmpty()
sni = httpupgradeSetting.host
httpupgradeSetting.path = path ?: "/"
httpupgradeSettings = httpupgradeSetting
}
"splithttp" -> {
val splithttpSetting = SplithttpSettingsBean()
splithttpSetting.host = host.orEmpty()
sni = splithttpSetting.host
splithttpSetting.path = path ?: "/"
splithttpSettings = splithttpSetting
}
"h2", "http" -> {
network = "h2"
val h2Setting = HttpSettingsBean()
h2Setting.host = (host.orEmpty()).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.orEmpty()
quicsetting.header.type = headerType ?: "none"
quicSettings = quicsetting
}
"grpc" -> {
val grpcSetting = GrpcSettingsBean()
grpcSetting.multiMode = mode == "multi"
grpcSetting.serviceName = serviceName.orEmpty()
grpcSetting.authority = authority.orEmpty()
grpcSetting.idle_timeout = 60
grpcSetting.health_check_timeout = 20
sni = authority.orEmpty()
grpcSettings = grpcSetting
}
}
return sni
}
fun populateTlsSettings(
streamSecurity: String, allowInsecure: Boolean, sni: String, fingerprint: String?, alpns: String?,
publicKey: String?, shortId: String?, spiderX: String?
) {
security = streamSecurity
val tlsSetting = TlsSettingsBean(
allowInsecure = allowInsecure,
serverName = sni,
fingerprint = fingerprint,
alpn = if (alpns.isNullOrEmpty()) null else alpns.split(",").map { it.trim() }.filter { it.isNotEmpty() },
publicKey = publicKey,
shortId = shortId,
spiderX = spiderX
)
if (security == TLS) {
tlsSettings = tlsSetting
realitySettings = null
} else if (security == REALITY) {
tlsSettings = null
realitySettings = tlsSetting
}
}
}
data class MuxBean(var enabled: Boolean)
data class MuxBean(
var enabled: Boolean,
var concurrency: Int = 8,
var xudpConcurrency: Int = 8,
var xudpProxyUDP443: String = "",
)
fun getServerAddress(): String? {
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, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
) {
return settings?.servers?.get(0)?.address
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
return settings?.peers?.get(0)?.endpoint?.substringBeforeLast(":")
}
return null
}
fun getServerPort(): Int? {
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, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.HTTP.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)
) {
return settings?.servers?.get(0)?.port
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
return settings?.peers?.get(0)?.endpoint?.substringAfterLast(":")?.toInt()
}
return null
}
fun getServerAddressAndPort(): String {
val address = getServerAddress().orEmpty()
val port = getServerPort()
return Utils.getIpv6Address(address) + ":" + port
}
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)
|| protocol.equals(EConfigType.HTTP.name, true)) {
return settings?.servers?.get(0)?.users?.get(0)?.pass
} else if (protocol.equals(EConfigType.WIREGUARD.name, true)) {
return settings?.secretKey
}
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)
|| protocol.equals(EConfigType.TROJAN.name, true)
|| protocol.equals(EConfigType.SHADOWSOCKS.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
)
}
"httpupgrade" -> {
val httpupgradeSetting = streamSettings?.httpupgradeSettings ?: return null
listOf(
"",
httpupgradeSetting.host,
httpupgradeSetting.path
)
}
"splithttp" -> {
val splithttpSetting = streamSettings?.splithttpSettings ?: return null
listOf(
"",
splithttpSetting.host,
splithttpSetting.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.authority.orEmpty(),
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>?)
data class ServersBean(
var address: 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>) {
data class RoutingBean(
var domainStrategy: String,
var 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 port: String? = null,
var inboundTag: ArrayList<String>? = null)
data class RulesBean(
var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null,
var outboundTag: String = "",
var balancerTag: String? = null,
var port: 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) {
data class PolicyBean(
var levels: Map<String, LevelBean>,
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 ->
EConfigType.entries.forEach {
if (outbound.protocol.equals(it.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)
}
}
@@ -1,13 +1,19 @@
package com.v2ray.ang.dto
data class VmessQRCode(var v: String = "",
var ps: String = "",
var add: String = "",
var port: String = "",
var id: String = "",
var aid: String = "",
var net: String = "",
var type: String = "",
var host: String = "",
var path: String = "",
var tls: String = "")
data class VmessQRCode(
var v: String = "",
var ps: String = "",
var add: String = "",
var port: String = "",
var id: String = "",
var aid: String = "0",
var scy: String = "",
var net: String = "",
var type: String = "",
var host: String = "",
var path: String = "",
var tls: String = "",
var sni: String = "",
var alpn: String = "",
var fp: String = ""
)
@@ -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)
}
}
@@ -2,63 +2,58 @@ package com.v2ray.ang.extension
import android.content.Context
import android.os.Build
import android.widget.Toast
import com.v2ray.ang.AngApplication
import me.dozen.dpreference.DPreference
import me.drakeet.support.toast.ToastCompat
import org.json.JSONObject
import java.net.URI
import java.net.URLConnection
/**
* Some extensions
*/
val Context.v2RayApplication: AngApplication?
get() = applicationContext as? AngApplication
val Context.v2RayApplication: AngApplication
get() = applicationContext as AngApplication
val Context.defaultDPreference: DPreference
get() = v2RayApplication.defaultDPreference
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
const val divisor = 1024F
fun Long.toSpeedString() = toTrafficString() + "/s"
fun Long.toTrafficString(): String {
if (this < threshold)
return "$this B"
val kib = this / divisor
if (kib < threshold)
return "${kib.toShortString()} KB"
val mib = kib / divisor
if (mib < threshold)
return "${mib.toShortString()} MB"
val gib = mib / divisor
if (gib < threshold)
return "${gib.toShortString()} GB"
val tib = gib / divisor
if (tib < threshold)
return "${tib.toShortString()} TB"
val pib = tib / divisor
if (pib < threshold)
return "${pib.toShortString()} PB"
return ""
fun Context.toast(message: Int) {
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
}
private fun Float.toShortString(): String {
val s = toString()
if (s.length <= 4)
return s
return s.substring(0, 4).removeSuffix(".")
fun Context.toast(message: CharSequence) {
ToastCompat.makeText(this, message, Toast.LENGTH_SHORT).show()
}
fun JSONObject.putOpt(pair: Pair<String, Any?>) {
put(pair.first, pair.second)
}
fun JSONObject.putOpt(pairs: Map<String, Any?>) {
pairs.forEach { put(it.key, it.value) }
}
const val THRESHOLD = 1000L
const val DIVISOR = 1024.0
fun Long.toSpeedString(): String = this.toTrafficString() + "/s"
fun Long.toTrafficString(): String {
val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB")
var size = this.toDouble()
var unitIndex = 0
while (size >= THRESHOLD && unitIndex < units.size - 1) {
size /= DIVISOR
unitIndex++
}
return String.format("%.1f %s", size, units[unitIndex])
}
val URLConnection.responseLength: Long
get() = if (Build.VERSION.SDK_INT >= 24) contentLengthLong else contentLength.toLong()
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
contentLengthLong
} else {
contentLength.toLong()
}
val URI.idnHost: String
get() = host?.replace("[", "")?.replace("]", "").orEmpty()
fun String.removeWhiteSpace(): String = replace("\\s+".toRegex(), "")
fun String.toLongEx(): Long = toLongOrNull() ?: 0
@@ -1,10 +0,0 @@
package com.v2ray.ang.extension
import android.preference.Preference
fun Preference.onClick(listener: () -> Unit) {
setOnPreferenceClickListener {
listener()
true
}
}
@@ -4,12 +4,13 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.text.TextUtils
import com.google.zxing.WriterException
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() {
override fun onReceive(context: Context, intent: Intent?) {
try {
@@ -21,14 +22,15 @@ class TaskerReceiver : BroadcastReceiver() {
return
} else if (switch) {
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
Utils.startVService(context)
Utils.startVServiceFromToggle(context)
} else {
Utils.startVService(context, guid)
MmkvManager.setSelectServer(guid)
V2RayServiceManager.startV2Ray(context)
}
} else {
Utils.stopVService(context)
}
} catch (e: WriterException) {
} catch (e: Exception) {
e.printStackTrace()
}
}
@@ -3,14 +3,15 @@ package com.v2ray.ang.receiver
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Build
import android.widget.RemoteViews
import com.v2ray.ang.R
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils
import org.jetbrains.anko.toast
class WidgetProvider : AppWidgetProvider() {
/**
@@ -18,11 +19,32 @@ class WidgetProvider : AppWidgetProvider() {
*/
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
}
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
val intent = Intent(AppConfig.BROADCAST_ACTION_WIDGET_CLICK)
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val intent = Intent(context, WidgetProvider::class.java)
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
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.image_switch, "setImageResource", R.drawable.ic_stop_24dp)
remoteViews.setInt(R.id.layout_background, "setBackgroundResource", R.drawable.ic_rounded_corner_active)
} else {
remoteViews.setInt(R.id.image_switch, "setImageResource", R.drawable.ic_play_24dp)
remoteViews.setInt(R.id.layout_background, "setBackgroundResource", R.drawable.ic_rounded_corner_inactive)
}
for (appWidgetId in appWidgetIds) {
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
@@ -30,21 +52,34 @@ class WidgetProvider : AppWidgetProvider() {
}
/**
* 接收窗口小部件点击时发送的广播
* 接收窗口小部件发送的广播
*/
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) {
val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")
if (isRunning) {
// context.toast(R.string.toast_services_stop)
if (V2RayServiceManager.v2rayPoint.isRunning) {
Utils.stopVService(context)
} else {
// context.toast(R.string.toast_services_start)
Utils.startVService(context)
Utils.startVServiceFromToggle(context)
}
} else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) {
AppWidgetManager.getInstance(context)?.let { manager ->
when (intent.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
updateWidgetBackground(
context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
true
)
}
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
updateWidgetBackground(
context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
false
)
}
}
}
}
}
}
@@ -6,19 +6,15 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.drawable.Icon
import android.net.VpnService
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import org.jetbrains.anko.toast
import java.lang.ref.SoftReference
@TargetApi(Build.VERSION_CODES.N)
class QSTileService : TileService() {
@@ -26,14 +22,13 @@ class QSTileService : TileService() {
if (state == Tile.STATE_INACTIVE) {
qsTile?.state = Tile.STATE_INACTIVE
qsTile?.label = getString(R.string.app_name)
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
} else if (state == Tile.STATE_ACTIVE) {
qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
qsTile?.label = V2RayServiceManager.currentConfig?.remarks
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
}
qsTile?.updateTile()
}
@@ -41,7 +36,12 @@ class QSTileService : TileService() {
super.onStartListening()
setState(Tile.STATE_INACTIVE)
mMsgReceive = ReceiveMessageHandler(this)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY), Context.RECEIVER_EXPORTED)
} else {
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
}
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
}
@@ -56,12 +56,9 @@ class QSTileService : TileService() {
super.onClick()
when (qsTile.state) {
Tile.STATE_INACTIVE -> {
val intent = VpnService.prepare(this)
if (intent == null)
if (!Utils.startVService(this)) {
toast(R.string.app_tile_first_use)
}
Utils.startVServiceFromToggle(this)
}
Tile.STATE_ACTIVE -> {
Utils.stopVService(this)
}
@@ -71,26 +68,30 @@ class QSTileService : TileService() {
private var mMsgReceive: BroadcastReceiver? = null
private class ReceiveMessageHandler(context: QSTileService) : BroadcastReceiver() {
internal var mReference: SoftReference<QSTileService> = SoftReference(context)
var mReference: SoftReference<QSTileService> = SoftReference(context)
override fun onReceive(ctx: Context?, intent: Intent?) {
val context = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
context?.setState(Tile.STATE_ACTIVE)
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
context?.setState(Tile.STATE_INACTIVE)
}
AppConfig.MSG_STATE_START_SUCCESS -> {
context?.setState(Tile.STATE_ACTIVE)
}
AppConfig.MSG_STATE_START_FAILURE -> {
context?.setState(Tile.STATE_INACTIVE)
}
AppConfig.MSG_STATE_STOP_SUCCESS -> {
context?.setState(Tile.STATE_INACTIVE)
}
}
}
}
}
}
@@ -0,0 +1,13 @@
package com.v2ray.ang.service
import android.app.Service
interface ServiceControl {
fun getService(): Service
fun startService()
fun stopService()
fun vpnProtect(socket: Int): Boolean
}
@@ -0,0 +1,69 @@
package com.v2ray.ang.service
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL
import com.v2ray.ang.AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
object SubscriptionUpdater {
class UpdateTask(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
private val notificationManager = NotificationManagerCompat.from(applicationContext)
private val notification =
NotificationCompat.Builder(applicationContext, SUBSCRIPTION_UPDATE_CHANNEL)
.setWhen(0)
.setTicker("Update")
.setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
.setSmallIcon(R.drawable.ic_stat_name)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
@SuppressLint("MissingPermission")
override suspend fun doWork(): Result {
Log.d(AppConfig.ANG_PACKAGE, "subscription automatic update starting")
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
for (i in subs) {
val subscription = i.second
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setChannelId(SUBSCRIPTION_UPDATE_CHANNEL)
val channel =
NotificationChannel(
SUBSCRIPTION_UPDATE_CHANNEL,
SUBSCRIPTION_UPDATE_CHANNEL_NAME,
NotificationManager.IMPORTANCE_MIN
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(3, notification.build())
Log.d(
AppConfig.ANG_PACKAGE,
"subscription automatic update: ---${subscription.remarks}"
)
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
AngConfigManager.importBatchConfig(configs, i.first, false)
notification.setContentText("Updating ${subscription.remarks}")
}
notificationManager.cancel(3)
return Result.success()
}
}
}
@@ -0,0 +1,56 @@
package com.v2ray.ang.service
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils
import java.lang.ref.SoftReference
class V2RayProxyOnlyService : Service(), ServiceControl {
override fun onCreate() {
super.onCreate()
V2RayServiceManager.serviceControl = SoftReference(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
V2RayServiceManager.startV2rayPoint()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
V2RayServiceManager.stopV2rayPoint()
}
override fun getService(): Service {
return this
}
override fun startService() {
// do nothing
}
override fun stopService() {
stopSelf()
}
override fun vpnProtect(socket: Int): Boolean {
return true
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
@RequiresApi(Build.VERSION_CODES.N)
override fun attachBaseContext(newBase: Context?) {
val context = newBase?.let {
MyContextWrapper.wrap(newBase, Utils.getLocale())
}
super.attachBaseContext(context)
}
}
@@ -0,0 +1,435 @@
package com.v2ray.ang.service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
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.dto.ServerConfig
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import libv2ray.V2RayPoint
import libv2ray.V2RayVPNServiceSupportsSet
import java.lang.ref.SoftReference
import kotlin.math.min
object V2RayServiceManager {
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
private const val NOTIFICATION_ICON_THRESHOLD = 3000
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback(), Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
private val mMsgReceive = ReceiveMessageHandler()
var serviceControl: SoftReference<ServiceControl>? = null
set(value) {
field = value
Seq.setContext(value?.get()?.getService()?.applicationContext)
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
}
var currentConfig: ServerConfig? = null
private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null
private var mDisposable: Disposable? = null
private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context) {
if (v2rayPoint.isRunning) return
val guid = MmkvManager.getSelectServer() ?: return
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
if (!result.status) return
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)
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
private class V2RayCallback : V2RayVPNServiceSupportsSet {
override fun shutdown(): Long {
val serviceControl = serviceControl?.get() ?: return -1
// called by go
return try {
serviceControl.stopService()
0
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
-1
}
}
override fun prepare(): Long {
return 0
}
override fun protect(l: Long): Boolean {
val serviceControl = serviceControl?.get() ?: return true
return serviceControl.vpnProtect(l.toInt())
}
override fun onEmitStatus(l: Long, s: String?): Long {
//Logger.d(s)
return 0
}
override fun setup(s: String): Long {
val serviceControl = serviceControl?.get() ?: return -1
//Logger.d(s)
return try {
serviceControl.startService()
lastQueryTime = System.currentTimeMillis()
startSpeedNotification()
0
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
-1
}
}
}
fun startV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
val guid = MmkvManager.getSelectServer() ?: return
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (v2rayPoint.isRunning) {
return
}
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
if (!result.status)
return
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
service.registerReceiver(mMsgReceive, mFilter, Context.RECEIVER_EXPORTED)
} else {
service.registerReceiver(mMsgReceive, mFilter)
}
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
v2rayPoint.configureFileContent = result.content
v2rayPoint.domainName = result.domainPort
currentConfig = config
try {
v2rayPoint.runLoop(settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) ?: false)
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification()
}
}
fun stopV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
if (v2rayPoint.isRunning) {
CoroutineScope(Dispatchers.IO).launch {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
}
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
cancelNotification()
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
}
private class ReceiveMessageHandler : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
val serviceControl = serviceControl?.get() ?: return
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> {
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
} else {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
}
}
AppConfig.MSG_UNREGISTER_CLIENT -> {
// nothing to do
}
AppConfig.MSG_STATE_START -> {
// nothing to do
}
AppConfig.MSG_STATE_STOP -> {
serviceControl.stopService()
}
AppConfig.MSG_STATE_RESTART -> {
startV2rayPoint()
}
AppConfig.MSG_MEASURE_DELAY -> {
measureV2rayDelay()
}
}
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.d(ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
stopSpeedNotification()
}
Intent.ACTION_SCREEN_ON -> {
Log.d(ANG_PACKAGE, "SCREEN_ON, start querying stats")
startSpeedNotification()
}
}
}
}
private fun measureV2rayDelay() {
CoroutineScope(Dispatchers.IO).launch {
val service = serviceControl?.get()?.getService() ?: return@launch
var time = -1L
var errstr = ""
if (v2rayPoint.isRunning) {
try {
time = v2rayPoint.measureDelay(Utils.getDelayTestUrl())
} catch (e: Exception) {
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
errstr = e.message?.substringAfter("\":") ?: "empty message"
}
if (time == -1L) {
try {
time = v2rayPoint.measureDelay(Utils.getDelayTestUrl(true))
} catch (e: Exception) {
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
errstr = e.message?.substringAfter("\":") ?: "empty message"
}
}
}
val result = if (time == -1L) {
service.getString(R.string.connection_test_error, errstr)
} else {
service.getString(R.string.connection_test_available, time)
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
}
}
private fun showNotification() {
val service = serviceControl?.get()?.getService() ?: return
val startMainIntent = Intent(service, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(
service,
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
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` = ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(
service,
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
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) {
createNotificationChannel()
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
mBuilder = NotificationCompat.Builder(service, channelId)
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(currentConfig?.remarks)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false)
.setOnlyAlertOnce(true)
.setContentIntent(contentPendingIntent)
.addAction(
R.drawable.ic_delete_24dp,
service.getString(R.string.notification_action_stop_v2ray),
stopV2RayPendingIntent
)
//.build()
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
service.startForeground(NOTIFICATION_ID, mBuilder?.build())
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = AppConfig.RAY_NG_CHANNEL_ID
val channelName = AppConfig.RAY_NG_CHANNEL_NAME
val chan = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_HIGH
)
chan.lightColor = Color.DKGRAY
chan.importance = NotificationManager.IMPORTANCE_NONE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
getNotificationManager()?.createNotificationChannel(chan)
return channelId
}
fun cancelNotification() {
val service = serviceControl?.get()?.getService() ?: return
service.stopForeground(true)
mBuilder = null
mDisposable?.dispose()
mDisposable = null
}
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
if (mBuilder != null) {
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
mBuilder?.setSmallIcon(R.drawable.ic_stat_name)
} else if (proxyTraffic > directTraffic) {
mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
} else {
mBuilder?.setSmallIcon(R.drawable.ic_stat_direct)
}
mBuilder?.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
mBuilder?.setContentText(contentText) // Emui4.1 need content text even if style is set as BigTextStyle
getNotificationManager()?.notify(NOTIFICATION_ID, mBuilder?.build())
}
}
private fun getNotificationManager(): NotificationManager? {
if (mNotificationManager == null) {
val service = serviceControl?.get()?.getService() ?: return null
mNotificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
return mNotificationManager
}
private fun startSpeedNotification() {
if (mDisposable == null &&
v2rayPoint.isRunning &&
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true
) {
var lastZeroSpeed = false
val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags?.remove(TAG_DIRECT)
mDisposable = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
val queryTime = System.currentTimeMillis()
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
var proxyTotal = 0L
val text = StringBuilder()
outboundTags?.forEach {
val up = v2rayPoint.queryStats(it, AppConfig.UPLINK)
val down = v2rayPoint.queryStats(it, AppConfig.DOWNLINK)
if (up + down > 0) {
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
proxyTotal += up + down
}
}
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.UPLINK)
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, AppConfig.DOWNLINK)
val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
}
appendSpeedString(
text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds
)
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
}
lastZeroSpeed = zeroSpeed
lastQueryTime = queryTime
}
}
}
private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
var n = name ?: "no tag"
n = n.substring(0, min(n.length, 6))
text.append(n)
for (i in n.length..6 step 2) {
text.append("\t")
}
text.append("${up.toLong().toSpeedString()}${down.toLong().toSpeedString()}\n")
}
private fun stopSpeedNotification() {
if (mDisposable != null) {
mDisposable?.dispose() //stop queryStats
mDisposable = null
updateNotification(currentConfig?.remarks, 0, 0)
}
}
}
@@ -0,0 +1,50 @@
package com.v2ray.ang.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import go.Seq
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import java.util.concurrent.Executors
class V2RayTestService : Service() {
private val realTestScope by lazy { CoroutineScope(Executors.newFixedThreadPool(10).asCoroutineDispatcher()) }
override fun onCreate() {
super.onCreate()
Seq.setContext(this)
Libv2ray.initV2Env(Utils.userAssetPath(this), Utils.getDeviceIdForXUDPBaseKey())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.getIntExtra("key", 0)) {
MSG_MEASURE_CONFIG -> {
val contentPair = intent.getSerializableExtra("content") as Pair<String, String>
realTestScope.launch {
val result = SpeedtestUtil.realPing(contentPair.second)
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result))
}
}
MSG_MEASURE_CONFIG_CANCEL -> {
realTestScope.coroutineContext[Job]?.cancelChildren()
}
}
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
@@ -1,126 +1,111 @@
package com.v2ray.ang.service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.*
import android.net.ConnectivityManager
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.VpnService
import android.os.*
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.StrictMode
import android.util.Log
import androidx.annotation.RequiresApi
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.PerAppProxyActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils
import libv2ray.Libv2ray
import libv2ray.V2RayVPNServiceSupportsSet
import rx.Observable
import rx.Subscription
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.lang.ref.SoftReference
import android.os.Build
import android.util.Log
import go.Seq
import org.jetbrains.anko.doAsync
class V2RayVpnService : VpnService() {
class V2RayVpnService : VpnService(), ServiceControl {
companion object {
const val NOTIFICATION_ID = 1
const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
fun startV2Ray(context: Context) {
val intent = Intent(context.applicationContext, V2RayVpnService::class.java)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
private const val VPN_MTU = 1500
private const val PRIVATE_VLAN4_CLIENT = "26.26.26.1"
private const val PRIVATE_VLAN4_ROUTER = "26.26.26.2"
private const val PRIVATE_VLAN6_CLIENT = "da26:2626::1"
private const val PRIVATE_VLAN6_ROUTER = "da26:2626::2"
private const val TUN2SOCKS = "libtun2socks.so"
}
private val v2rayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
private lateinit var configContent: String
private lateinit var mInterface: ParcelFileDescriptor
val fd: Int get() = mInterface.fd
private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null
private var isRunning = false
//val fd: Int get() = mInterface.fd
private lateinit var process: Process
/**
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
*
* This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that
* satisfies default network capabilities but only THE default network. Unfortunately we need to have
* android.permission.CHANGE_NETWORK_STATE to be able to call requestNetwork.
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/
private val defaultNetworkRequest by lazy @RequiresApi(Build.VERSION_CODES.P) {
/**destroy
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
*
* This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that
* satisfies default network capabilities but only THE default network. Unfortunately we need to have
* android.permission.CHANGE_NETWORK_STATE to be able to call requestNetwork.
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/
@delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkRequest by lazy {
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
}
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
private val defaultNetworkCallback by lazy @RequiresApi(Build.VERSION_CODES.P) {
@delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkCallback by lazy {
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
setUnderlyingNetworks(arrayOf(network))
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
// it's a good idea to refresh capabilities
setUnderlyingNetworks(arrayOf(network))
}
override fun onLost(network: Network) {
setUnderlyingNetworks(null)
}
}
}
private var listeningForDefaultNetwork = false
override fun onCreate() {
super.onCreate()
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
v2rayPoint.packageName = Utils.packagePath(applicationContext)
Seq.setContext(applicationContext)
V2RayServiceManager.serviceControl = SoftReference(this)
}
override fun onRevoke() {
stopV2Ray()
}
override fun onLowMemory() {
stopV2Ray()
super.onLowMemory()
}
// override fun onLowMemory() {
// stopV2Ray()
// super.onLowMemory()
// }
override fun onDestroy() {
super.onDestroy()
cancelNotification()
V2RayServiceManager.cancelNotification()
}
fun setup(parameters: String) {
val prepare = VpnService.prepare(this)
private fun setup() {
val prepare = prepare(this)
if (prepare != null) {
return
}
@@ -128,33 +113,46 @@ class V2RayVpnService : VpnService() {
// 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 enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
parameters.split(" ")
.map { it.split(",") }
builder.setMtu(VPN_MTU)
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
val bypassLan = SettingsManager.routingRulesetsBypassLan()
if (bypassLan) {
resources.getStringArray(R.array.bypass_private_ip_address).forEach {
val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}
} else {
builder.addRoute("0.0.0.0", 0)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
if (bypassLan) {
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
} else {
builder.addRoute("::", 0)
}
}
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
} else {
Utils.getVpnDnsServers()
.forEach {
when (it[0][0]) {
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
's' -> builder.addSearchDomain(it[1])
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2]))
'r' -> builder.addRoute(it[1], Integer.parseInt(it[2]))
'd' -> builder.addDnsServer(it[1])
if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it)
}
}
if(!enableLocalDns) {
Utils.getRemoteDnsServers(defaultDPreference)
.forEach {
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)
@@ -171,33 +169,90 @@ class V2RayVpnService : VpnService() {
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
listeningForDefaultNetwork = true
try {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
} catch (e: Exception) {
e.printStackTrace()
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false)
}
// Create a new interface using the builder and save the parameters.
mInterface = builder.establish()
sendFd()
startSpeedNotification()
try {
mInterface = builder.establish()!!
isRunning = true
runTun2socks()
} catch (e: Exception) {
// non-nullable lateinit var
e.printStackTrace()
stopV2Ray()
}
}
fun shutdown() {
stopV2Ray(true)
private fun runTun2socks() {
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
val cmd = arrayListOf(
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
"--netif-netmask", "255.255.255.252",
"--socks-server-addr", "127.0.0.1:${socksPort}",
"--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
"--enable-udprelay",
"--loglevel", "notice"
)
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
cmd.add("--netif-ip6addr")
cmd.add(PRIVATE_VLAN6_ROUTER)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
cmd.add("--dnsgw")
cmd.add("127.0.0.1:${localDnsPort}")
}
Log.d(packageName, cmd.toString())
try {
val proBuilder = ProcessBuilder(cmd)
proBuilder.redirectErrorStream(true)
process = proBuilder
.directory(applicationContext.filesDir)
.start()
Thread(Runnable {
Log.d(packageName, "$TUN2SOCKS check")
process.waitFor()
Log.d(packageName, "$TUN2SOCKS exited")
if (isRunning) {
Log.d(packageName, "$TUN2SOCKS restart")
runTun2socks()
}
}).start()
Log.d(packageName, process.toString())
sendFd()
} catch (e: Exception) {
Log.d(packageName, e.toString())
}
}
fun sendFd() {
private fun sendFd() {
val fd = mInterface.fileDescriptor
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
val path = File(applicationContext.filesDir, "sock_path").absolutePath
Log.d(packageName, path)
doAsync {
CoroutineScope(Dispatchers.IO).launch {
var tries = 0
while (true) try {
Thread.sleep(50L shl tries)
Log.d(packageName, "sendFd tries: " + tries.toString())
Log.d(packageName, "sendFd tries: $tries")
LocalSocket().use { localSocket ->
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
localSocket.setFileDescriptorsForSend(arrayOf(fd))
@@ -213,74 +268,35 @@ class V2RayVpnService : VpnService() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startV2ray()
V2RayServiceManager.startV2rayPoint()
return START_STICKY
//return super.onStartCommand(intent, flags, startId)
}
private fun startV2ray() {
if (!v2rayPoint.isRunning) {
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
registerReceiver(mMsgReceive, mFilter)
} catch (e: Exception) {
}
configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
v2rayPoint.configureFileContent = configContent
v2rayPoint.enableLocalDNS = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
v2rayPoint.forwardIpv6 = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
v2rayPoint.domainName = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
try {
v2rayPoint.runLoop()
} catch (e: Exception) {
Log.d(packageName, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else {
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification()
}
}
// showNotification()
}
private fun stopV2Ray(isForced: Boolean = true) {
// val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "")
// val emptyInfo = VpnNetworkInfo()
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
// saveVpnNetworkInfo(configName, info)
isRunning = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (listeningForDefaultNetwork) {
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
listeningForDefaultNetwork = false
}
}
if (v2rayPoint.isRunning) {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(packageName, e.toString())
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
} catch (ignored: Exception) {
// ignored
}
}
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "")
cancelNotification()
try {
Log.d(packageName, "tun2socks destroy")
process.destroy()
} catch (e: Exception) {
Log.d(packageName, e.toString())
}
V2RayServiceManager.stopV2rayPoint()
if (isForced) {
try {
unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
}
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
//It's strage but true.
//This can be verified by putting stopself() behind and call stopLoop and startLoop
@@ -291,207 +307,32 @@ class V2RayVpnService : VpnService() {
try {
mInterface.close()
} catch (ignored: Exception) {
}
}
}
private fun showNotification() {
val startMainIntent = Intent(applicationContext, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(applicationContext,
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(applicationContext,
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
mBuilder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_v)
.setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false)
.setOnlyAlertOnce(true)
.setContentIntent(contentPendingIntent)
.addAction(R.drawable.ic_close_grey_800_24dp,
getString(R.string.notification_action_stop_v2ray),
stopV2RayPendingIntent)
//.build()
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
startForeground(NOTIFICATION_ID, mBuilder?.build())
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "RAY_NG_M_CH_ID"
val channelName = "V2rayNG Background Service"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH)
chan.lightColor = Color.DKGRAY
chan.importance = NotificationManager.IMPORTANCE_NONE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
getNotificationManager().createNotificationChannel(chan)
return channelId
}
private fun cancelNotification() {
stopForeground(true)
mBuilder = null
mSubscription?.unsubscribe()
mSubscription = null
}
private fun updateNotification(contentText: String) {
if (mBuilder != null) {
mBuilder?.setContentTitle(contentText)
getNotificationManager().notify(NOTIFICATION_ID, mBuilder?.build())
}
}
private fun getNotificationManager(): NotificationManager {
if (mNotificationManager == null) {
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
return mNotificationManager!!
}
fun startSpeedNotification() {
if (mSubscription == null &&
v2rayPoint.isRunning &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
var last_zero_speed = false
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
val uplink = v2rayPoint.queryStats("socks", "uplink")
val downlink = v2rayPoint.queryStats("socks", "downlink")
val zero_speed = (uplink == 0L && downlink == 0L)
if (!zero_speed || !last_zero_speed) {
updateNotification("${cf_name}${(uplink / 3).toSpeedString()}${(downlink / 3).toSpeedString()}")
}
last_zero_speed = zero_speed
}
}
}
fun stopSpeedNotification() {
if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats
mSubscription = null
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
updateNotification(cf_name)
}
}
private inner class V2RayCallback : V2RayVPNServiceSupportsSet {
override fun shutdown(): Long {
// called by go
// shutdown the whole vpn service
try {
this@V2RayVpnService.shutdown()
return 0
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
// ignored
}
}
override fun prepare(): Long {
return 0
}
override fun protect(l: Long) = (if (this@V2RayVpnService.protect(l.toInt())) 0 else 1).toLong()
override fun onEmitStatus(l: Long, s: String?): Long {
//Logger.d(s)
return 0
}
override fun setup(s: String): Long {
//Logger.d(s)
try {
this@V2RayVpnService.setup(s)
return 0
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
}
override fun sendFd(): Long {
try {
this@V2RayVpnService.sendFd()
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
return 0
}
}
private var mMsgReceive = ReceiveMessageHandler(this@V2RayVpnService)
override fun getService(): Service {
return this
}
private class ReceiveMessageHandler(vpnService: V2RayVpnService) : BroadcastReceiver() {
internal var mReference: SoftReference<V2RayVpnService> = SoftReference(vpnService)
override fun startService() {
setup()
}
override fun onReceive(ctx: Context?, intent: Intent?) {
val vpnService = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> {
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
override fun stopService() {
stopV2Ray(true)
}
val isRunning = vpnService?.v2rayPoint!!.isRunning
&& VpnService.prepare(vpnService) == null
if (isRunning) {
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_RUNNING, "")
} else {
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_NOT_RUNNING, "")
}
}
AppConfig.MSG_UNREGISTER_CLIENT -> {
// vpnService?.mMsgSend = null
}
AppConfig.MSG_STATE_START -> {
//nothing to do
}
AppConfig.MSG_STATE_STOP -> {
vpnService?.stopV2Ray()
}
AppConfig.MSG_STATE_RESTART -> {
vpnService?.startV2ray()
}
}
override fun vpnProtect(socket: Int): Boolean {
return protect(socket)
}
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
vpnService?.stopSpeedNotification()
}
Intent.ACTION_SCREEN_ON -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
vpnService?.startSpeedNotification()
}
}
@RequiresApi(Build.VERSION_CODES.N)
override fun attachBaseContext(newBase: Context?) {
val context = newBase?.let {
MyContextWrapper.wrap(newBase, Utils.getLocale())
}
super.attachBaseContext(context)
}
}
@@ -0,0 +1,175 @@
package com.v2ray.ang.ui
import android.Manifest
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import com.tbruyelle.rxpermissions3.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityAboutBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.SpeedtestUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.ZipUtil
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
class AboutActivity : BaseActivity() {
private val binding by lazy { ActivityAboutBinding.inflate(layoutInflater) }
private val extDir by lazy { File(Utils.backupPath(this)) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
title = getString(R.string.title_about)
binding.tvBackupSummary.text = this.getString(R.string.summary_configuration_backup, extDir)
binding.layoutBackup.setOnClickListener {
val ret = backupConfiguration(extDir.absolutePath)
if (ret.first) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
}
binding.layoutShare.setOnClickListener {
val ret = backupConfiguration(cacheDir.absolutePath)
if (ret.first) {
startActivity(
Intent.createChooser(
Intent(Intent.ACTION_SEND).setType("application/zip")
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(
Intent.EXTRA_STREAM, FileProvider.getUriForFile(
this, BuildConfig.APPLICATION_ID + ".cache", File(ret.second)
)
), getString(R.string.title_configuration_share)
)
)
} else {
toast(R.string.toast_failure)
}
}
binding.layoutRestore.setOnClickListener {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(permission)
.subscribe {
if (it) {
try {
showFileChooser()
} catch (e: Exception) {
e.printStackTrace()
}
} else
toast(R.string.toast_permission_denied)
}
}
binding.layoutSoureCcode.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGUrl)
}
binding.layoutFeedback.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGIssues)
}
binding.layoutTgChannel.setOnClickListener {
Utils.openUri(this, AppConfig.TgChannelUrl)
}
binding.layoutPrivacyPolicy.setOnClickListener {
Utils.openUri(this, AppConfig.v2rayNGPrivacyPolicy)
}
"v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})".also {
binding.tvVersion.text = it
}
}
fun backupConfiguration(outputZipFilePos: String): Pair<Boolean, String> {
val dateFormated = SimpleDateFormat(
"yyyy-MM-dd-HH-mm-ss",
Locale.getDefault()
).format(System.currentTimeMillis())
val folderName = "${getString(R.string.app_name)}_${dateFormated}"
val backupDir = this.cacheDir.absolutePath + "/$folderName"
val outputZipFilePath = "$outputZipFilePos/$folderName.zip"
val count = MMKV.backupAllToDirectory(backupDir)
if (count <= 0) {
return Pair(false, "")
}
if (ZipUtil.zipFromFolder(backupDir, outputZipFilePath)) {
return Pair(true, outputZipFilePath)
} else {
return Pair(false, "")
}
}
fun restoreConfiguration(zipFile: File): Boolean {
val backupDir = this.cacheDir.absolutePath + "/${System.currentTimeMillis()}"
if (!ZipUtil.unzipToFolder(zipFile, backupDir)) {
return false
}
val count = MMKV.restoreAllFromDirectory(backupDir)
return count > 0
}
private fun showFileChooser() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
chooseFile.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager)
}
}
private val chooseFile =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
try {
try {
val targetFile =
File(this.cacheDir.absolutePath, "${System.currentTimeMillis()}.zip")
contentResolver.openInputStream(uri).use { input ->
targetFile.outputStream().use { fileOut ->
input?.copyTo(fileOut)
}
}
if (restoreConfiguration(targetFile)) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
} catch (e: Exception) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
toast(e.message.toString())
}
}
}
}
@@ -1,14 +1,42 @@
package com.v2ray.ang.ui
import android.support.v7.app.AppCompatActivity
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (!Utils.getDarkModeStatus(this)) {
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = true
}
}
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
android.R.id.home -> {
onBackPressed()
// Handles the home button press by delegating to the onBackPressedDispatcher.
// This ensures consistent back navigation behavior.
onBackPressedDispatcher.onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}
@RequiresApi(Build.VERSION_CODES.N)
override fun attachBaseContext(newBase: Context?) {
val context = newBase?.let {
MyContextWrapper.wrap(newBase, Utils.getLocale())
}
super.attachBaseContext(context)
}
}
@@ -1,206 +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.InappBuyActivity
import com.v2ray.ang.R
import org.jetbrains.anko.startActivity
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.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> {
startActivity<LogcatActivity>()
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 (MainActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.server_profile)
} else if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.sub_setting)
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.settings)
}
}
}
@@ -1,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]
}
}
}
@@ -7,40 +7,45 @@ import android.text.method.ScrollingMovementMethod
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.lifecycle.lifecycleScope
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 org.jetbrains.anko.doAsync
import org.jetbrains.anko.toast
import org.jetbrains.anko.uiThread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import java.util.LinkedHashSet
class LogcatActivity : BaseActivity() {
private val binding by lazy {
ActivityLogcatBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_logcat)
setContentView(binding.root)
title = getString(R.string.title_logcat)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
logcat(false)
}
private fun logcat(shouldFlushLog: Boolean) {
try {
pb_waiting.visibility = View.VISIBLE
binding.pbWaiting.visibility = View.VISIBLE
doAsync {
lifecycleScope.launch(Dispatchers.Default) {
if (shouldFlushLog) {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-c")
val process = Runtime.getRuntime().exec(lst.toTypedArray())
process.waitFor()
withContext(Dispatchers.IO) {
val process = Runtime.getRuntime().exec(lst.toTypedArray())
process.waitFor()
}
}
val lst = LinkedHashSet<String>()
lst.add("logcat")
@@ -48,17 +53,19 @@ class LogcatActivity : BaseActivity() {
lst.add("-v")
lst.add("time")
lst.add("-s")
lst.add("GoLog,tun2socks,com.v2ray.ang")
val process = Runtime.getRuntime().exec(lst.toTypedArray())
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
val process = withContext(Dispatchers.IO) {
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() }
uiThread {
tv_logcat.text = allText
tv_logcat.movementMethod = ScrollingMovementMethod()
pb_waiting.visibility = View.GONE
Handler(Looper.getMainLooper()).post { sv_logcat.fullScroll(View.FOCUS_DOWN) }
launch(Dispatchers.Main) {
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,21 +73,23 @@ 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
}
R.id.delete -> {
R.id.clear_all -> {
logcat(true)
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -1,299 +1,475 @@
package com.v2ray.ang.ui
import android.Manifest
import android.content.*
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.res.ColorStateList
import android.net.Uri
import android.net.VpnService
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_main.*
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.navigation.NavigationView
import com.google.android.material.tabs.TabLayout
import com.tbruyelle.rxpermissions3.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.V2rayConfigUtil
import org.jetbrains.anko.*
import java.lang.ref.SoftReference
import java.net.URL
import android.content.IntentFilter
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.helper.ItemTouchHelper
import android.util.Log
//import com.v2ray.ang.InappBuyActivity
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
import com.v2ray.ang.R
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.util.AngConfigManager.configs
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import me.drakeet.support.toast.ToastCompat
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 val binding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
var isRunning = false
set(value) {
field = value
adapter.changeable = !value
if (value) {
fab.imageResource = R.drawable.ic_v
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.imageResource = R.drawable.ic_v_idle
tv_test_state.text = getString(R.string.connection_not_connected)
private val adapter by lazy { MainRecyclerAdapter(this) }
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
startV2Ray()
}
}
private val requestSubSettingActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
initGroupTab()
}
private val tabGroupListener = object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val selectId = tab?.tag.toString()
if (selectId != mainViewModel.subscriptionId) {
mainViewModel.subscriptionIdChanged(selectId)
}
hideCircle()
}
private val adapter by lazy { MainRecyclerAdapter(this) }
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
}
private var mItemTouchHelper: ItemTouchHelper? = null
val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setContentView(binding.root)
title = getString(R.string.title_server)
setSupportActionBar(toolbar)
setSupportActionBar(binding.toolbar)
fab.setOnClickListener {
if (isRunning) {
binding.fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this)
} else {
} 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 {
if (isRunning) {
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
tv_test_state.text = getString(R.string.connection_test_testing)
doAsync {
val result = Utils.testConnection(this@MainActivity, socksPort)
uiThread {
tv_test_state.text = Utils.getEditable(result)
}
}
binding.layoutTest.setOnClickListener {
if (mainViewModel.isRunning.value == true) {
setTestState(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
val callback = SimpleItemTouchHelperCallback(adapter)
mItemTouchHelper = ItemTouchHelper(callback)
mItemTouchHelper?.attachToRecyclerView(recycler_view)
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter))
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)
binding.navView.setNavigationItemSelectedListener(this)
initGroupTab()
setupViewModel()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
RxPermissions(this)
.request(Manifest.permission.POST_NOTIFICATIONS)
.subscribe {
if (!it)
toast(R.string.toast_permission_denied)
}
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
//super.onBackPressed()
onBackPressedDispatcher.onBackPressed()
}
}
})
}
private fun setupViewModel() {
mainViewModel.updateListAction.observe(this) { index ->
if (index >= 0) {
adapter.notifyItemChanged(index)
} else {
adapter.notifyDataSetChanged()
}
}
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
mainViewModel.isRunning.observe(this) { isRunning ->
adapter.isRunning = isRunning
if (isRunning) {
binding.fab.setImageResource(R.drawable.ic_stop_24dp)
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_active))
setTestState(getString(R.string.connection_connected))
binding.layoutTest.isFocusable = true
} else {
binding.fab.setImageResource(R.drawable.ic_play_24dp)
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_inactive))
setTestState(getString(R.string.connection_not_connected))
binding.layoutTest.isFocusable = false
}
}
mainViewModel.startListenBroadcast()
mainViewModel.copyAssets(assets)
}
private fun initGroupTab() {
binding.tabGroup.removeOnTabSelectedListener(tabGroupListener)
binding.tabGroup.removeAllTabs()
binding.tabGroup.isVisible = false
val (listId, listRemarks) = mainViewModel.getSubscriptions(this)
if (listId == null || listRemarks == null) {
return
}
for (it in listRemarks.indices) {
val tab = binding.tabGroup.newTab()
tab.text = listRemarks[it]
tab.tag = listId[it]
binding.tabGroup.addTab(tab)
}
val selectIndex =
listId.indexOf(mainViewModel.subscriptionId).takeIf { it >= 0 } ?: (listId.count() - 1)
binding.tabGroup.selectTab(binding.tabGroup.getTabAt(selectIndex))
binding.tabGroup.addOnTabSelectedListener(tabGroupListener)
binding.tabGroup.isVisible = true
}
fun startV2Ray() {
if (AngConfigManager.configs.index < 0) {
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
return
}
showCircle()
// toast(R.string.toast_services_start)
if (!Utils.startVService(this)) {
hideCircle()
}
V2RayServiceManager.startV2Ray(this)
}
override fun onStart() {
super.onStart()
isRunning = false
// val intent = Intent(this.applicationContext, V2RayVpnService::class.java)
// intent.`package` = AppConfig.ANG_PACKAGE
// bindService(intent, mConnection, BIND_AUTO_CREATE)
mMsgReceive = ReceiveMessageHandler(this@MainActivity)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onStop() {
super.onStop()
if (mMsgReceive != null) {
unregisterReceiver(mMsgReceive)
mMsgReceive = null
fun restartV2Ray() {
if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this)
}
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
startV2Ray()
}
}
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 -> {
if (resultCode == RESULT_OK) {
val uri = data!!.data
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
val searchItem = menu.findItem(R.id.search_view)
if (searchItem != null) {
val searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
mainViewModel.filterConfig(newText.orEmpty())
return false
}
})
searchView.setOnCloseListener {
mainViewModel.filterConfig("")
false
}
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.import_qrcode -> {
importQRcode(REQUEST_SCAN)
importQRcode(true)
true
}
R.id.import_clipboard -> {
importClipboard()
true
}
R.id.import_manually_vmess -> {
startActivity<ServerActivity>("position" to -1, "isRunning" to isRunning)
adapter.updateConfigList()
importManually(EConfigType.VMESS.value)
true
}
R.id.import_manually_vless -> {
importManually(EConfigType.VLESS.value)
true
}
R.id.import_manually_ss -> {
startActivity<Server3Activity>("position" to -1, "isRunning" to isRunning)
adapter.updateConfigList()
importManually(EConfigType.SHADOWSOCKS.value)
true
}
R.id.import_manually_socks -> {
startActivity<Server4Activity>("position" to -1, "isRunning" to isRunning)
adapter.updateConfigList()
importManually(EConfigType.SOCKS.value)
true
}
R.id.import_manually_http -> {
importManually(EConfigType.HTTP.value)
true
}
R.id.import_manually_trojan -> {
importManually(EConfigType.TROJAN.value)
true
}
R.id.import_manually_wireguard -> {
importManually(EConfigType.WIREGUARD.value)
true
}
R.id.import_config_custom_clipboard -> {
importConfigCustomClipboard()
true
}
R.id.import_config_custom_local -> {
importConfigCustomLocal()
true
}
R.id.import_config_custom_url -> {
importConfigCustomUrlClipboard()
true
}
R.id.import_config_custom_url_scan -> {
importQRcode(REQUEST_SCAN_URL)
importQRcode(false)
true
}
// R.id.sub_setting -> {
// startActivity<SubSettingActivity>()
// true
// }
R.id.sub_update -> {
importConfigViaSub()
true
}
R.id.export_all -> {
if (AngConfigManager.shareAll2Clipboard() == 0) {
//remove toast, otherwise it will block previous warning message
} else {
toast(R.string.toast_failure)
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
val ret = mainViewModel.exportAllServer()
launch(Dispatchers.Main) {
if (ret == 0)
toast(R.string.toast_success)
else
toast(R.string.toast_failure)
binding.pbWaiting.hide()
}
}
true
}
R.id.ping_all -> {
for (k in 0 until configs.vmess.count()) {
configs.vmess[k].testResult = ""
adapter.updateConfigList()
}
for (k in 0 until configs.vmess.count()) {
if (configs.vmess[k].configType != AppConfig.EConfigType.Custom) {
doAsync {
configs.vmess[k].testResult = Utils.tcping(configs.vmess[k].address, configs.vmess[k].port)
uiThread {
adapter.updateSelectedItem(k)
mainViewModel.testAllTcping()
true
}
R.id.real_ping_all -> {
mainViewModel.testAllRealPing()
true
}
R.id.service_restart -> {
restartV2Ray()
true
}
R.id.del_all_config -> {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
mainViewModel.removeAllServer()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
binding.pbWaiting.hide()
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
true
}
R.id.del_duplicate_config -> {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
val ret = mainViewModel.removeDuplicateServer()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
toast(getString(R.string.title_del_duplicate_config_count, ret))
binding.pbWaiting.hide()
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
true
}
R.id.del_invalid_config -> {
AlertDialog.Builder(this).setMessage(R.string.del_invalid_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
mainViewModel.removeInvalidServer()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
binding.pbWaiting.hide()
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
true
}
R.id.sort_by_test_results -> {
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
mainViewModel.sortByTestResults()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
binding.pbWaiting.hide()
}
}
true
}
// R.id.settings -> {
// startActivity<SettingsActivity>("isRunning" to isRunning)
// true
// }
// R.id.logcat -> {
// startActivity<LogcatActivity>()
// true
// }
else -> super.onOptionsItemSelected(item)
}
private fun importManually(createConfigType: Int) {
startActivity(
Intent()
.putExtra("createConfigType", createConfigType)
.putExtra("subscriptionId", mainViewModel.subscriptionId)
.setClass(this, ServerActivity::class.java)
)
}
/**
* import config from qrcode
*/
fun importQRcode(requestCode: Int): Boolean {
private fun importQRcode(forConfig: 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(this)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult<ScannerActivity>(requestCode)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
if (forConfig)
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
// }
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
*/
fun importClipboard()
private fun importClipboard()
: Boolean {
try {
val clipboard = Utils.getClipboard(this)
@@ -305,17 +481,32 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true
}
fun importBatchConfig(server: String?, subid: String = "") {
val count = AngConfigManager.importBatchConfig(server, subid)
if (count > 0) {
toast(R.string.toast_success)
adapter.updateConfigList()
} else {
toast(R.string.toast_failure)
private fun importBatchConfig(server: String?) {
// val dialog = AlertDialog.Builder(this)
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
// .setCancelable(false)
// .show()
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
val (count, countSub) = AngConfigManager.importBatchConfig(server, mainViewModel.subscriptionId, true)
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
} else if (countSub > 0) {
initGroupTab()
} else {
toast(R.string.toast_failure)
}
//dialog.dismiss()
binding.pbWaiting.hide()
}
}
}
fun importConfigCustomClipboard()
private fun importConfigCustomClipboard()
: Boolean {
try {
val configText = Utils.getClipboard(this)
@@ -334,7 +525,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/**
* import config from local config file
*/
fun importConfigCustomLocal(): Boolean {
private fun importConfigCustomLocal(): Boolean {
try {
showFileChooser()
} catch (e: Exception) {
@@ -344,7 +535,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true
}
fun importConfigCustomUrlClipboard()
private fun importConfigCustomUrlClipboard()
: Boolean {
try {
val url = Utils.getClipboard(this)
@@ -362,15 +553,20 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/**
* import config from url
*/
fun importConfigCustomUrl(url: String?): Boolean {
private fun importConfigCustomUrl(url: String?): Boolean {
try {
if (!Utils.isValidUrl(url)) {
toast(R.string.toast_invalid_url)
return false
}
doAsync {
val configText = URL(url).readText()
uiThread {
lifecycleScope.launch(Dispatchers.IO) {
val configText = try {
Utils.getUrlContentWithCustomUserAgent(url)
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
importCustomizeConfig(configText)
}
}
@@ -384,34 +580,26 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/**
* import config from sub
*/
fun importConfigViaSub()
: 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)
) {
continue
}
val id = subItem[k].id
val url = subItem[k].url
if (!Utils.isValidUrl(url)) {
continue
}
Log.d("Main", url)
doAsync {
val configText = URL(url).readText()
uiThread {
importBatchConfig(Utils.decode(configText), id)
}
private fun importConfigViaSub(): Boolean {
// val dialog = AlertDialog.Builder(this)
// .setView(LayoutProgressBinding.inflate(layoutInflater).root)
// .setCancelable(false)
// .show()
binding.pbWaiting.show()
lifecycleScope.launch(Dispatchers.IO) {
val count = mainViewModel.updateConfigViaSubAll()
delay(500L)
launch(Dispatchers.Main) {
if (count > 0) {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
} else {
toast(R.string.toast_failure)
}
//dialog.dismiss()
binding.pbWaiting.hide()
}
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
@@ -425,52 +613,69 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(
Intent.createChooser(intent, getString(R.string.title_file_chooser)),
REQUEST_FILE_CHOOSER)
} catch (ex: android.content.ActivityNotFoundException) {
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} catch (ex: 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
*/
private fun readContentFromUri(uri: Uri) {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
RxPermissions(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
.subscribe {
if (it) {
try {
val inputStream = contentResolver.openInputStream(uri)
val configText = inputStream.bufferedReader().readText()
importCustomizeConfig(configText)
} catch (e: Exception) {
e.printStackTrace()
.request(permission)
.subscribe {
if (it) {
try {
contentResolver.openInputStream(uri).use { input ->
importCustomizeConfig(input?.bufferedReader()?.readText())
}
} else
toast(R.string.toast_permission_denied)
}
} catch (e: Exception) {
e.printStackTrace()
}
} else
toast(R.string.toast_permission_denied)
}
}
/**
* import customize config
*/
fun importCustomizeConfig(server: String?) {
if (server == null) {
private fun importCustomizeConfig(server: String?) {
try {
if (server == null || TextUtils.isEmpty(server)) {
toast(R.string.toast_none_data)
return
}
if (mainViewModel.appendCustomConfigServer(server)) {
mainViewModel.reloadServerList()
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
//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
}
if (!V2rayConfigUtil.isValidConfig(server)) {
toast(R.string.toast_config_file_invalid)
return
}
val resId = AngConfigManager.importCustomizeConfig(server)
if (resId > 0) {
toast(resId)
} else {
toast(R.string.toast_success)
adapter.updateConfigList()
}
}
private fun setTestState(content: String?) {
binding.tvTestState.text = content
}
// val mConnection = object : ServiceConnection {
@@ -482,92 +687,47 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// }
// }
private
var mMsgReceive: BroadcastReceiver? = null
private class ReceiveMessageHandler(activity: MainActivity) : BroadcastReceiver() {
internal var mReference: SoftReference<MainActivity> = SoftReference(activity)
override fun onReceive(ctx: Context?, intent: Intent?) {
val activity = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
activity?.isRunning = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
activity?.isRunning = false
}
AppConfig.MSG_STATE_START_SUCCESS -> {
activity?.toast(R.string.toast_services_success)
activity?.isRunning = true
}
AppConfig.MSG_STATE_START_FAILURE -> {
activity?.toast(R.string.toast_services_failure)
activity?.isRunning = false
}
AppConfig.MSG_STATE_STOP_SUCCESS -> {
activity?.isRunning = false
}
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B) {
moveTaskToBack(false)
return true
}
return super.onKeyDown(keyCode, event)
}
fun showCircle() {
fabProgressCircle?.show()
}
fun hideCircle() {
try {
Observable.timer(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (fabProgressCircle.isShown) {
fabProgressCircle.hide()
}
}
} catch (e: Exception) {
}
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
//R.id.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> {
startActivity<SubSettingActivity>()
requestSubSettingActivity.launch(Intent(this, SubSettingActivity::class.java))
}
R.id.settings -> {
startActivity<SettingsActivity>("isRunning" to isRunning)
startActivity(
Intent(this, SettingsActivity::class.java)
.putExtra("isRunning", mainViewModel.isRunning.value == true)
)
}
R.id.feedback -> {
Utils.openUri(this, AppConfig.v2rayNGIssues)
R.id.routing_setting -> {
requestSubSettingActivity.launch(Intent(this, RoutingSettingActivity::class.java))
}
R.id.promotion -> {
Utils.openUri(this, AppConfig.promotionUrl)
}
R.id.donate -> {
// startActivity<InappBuyActivity>()
Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}")
}
R.id.logcat -> {
startActivity<LogcatActivity>()
startActivity(Intent(this, LogcatActivity::class.java))
}
R.id.about -> {
startActivity(Intent(this, AboutActivity::class.java))
}
}
drawer_layout.closeDrawer(GravityCompat.START)
binding.drawerLayout.closeDrawer(GravityCompat.START)
return true
}
}
}
@@ -1,268 +1,244 @@
package com.v2ray.ang.ui
import android.content.Intent
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.AngApplication.Companion.application
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.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.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.item_qrcode.view.*
import kotlinx.android.synthetic.main.item_recycler_main.view.*
import org.jetbrains.anko.*
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import java.util.concurrent.TimeUnit
import com.v2ray.ang.extension.defaultDPreference
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
, ItemTouchHelperAdapter {
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
companion object {
private const val VIEW_TYPE_ITEM = 1
private const val VIEW_TYPE_FOOTER = 2
}
private var mActivity: MainActivity = activity
private lateinit var configs: AngConfig
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.serversCache.size + 1
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) {
val configType = 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.serversCache[position].guid
val profile = mActivity.mainViewModel.serversCache[position].profile
// //filter
// if (mActivity.mainViewModel.subscriptionId.isNotEmpty()
// && mActivity.mainViewModel.subscriptionId != config.subscriptionId
// ) {
// holder.itemMainBinding.cardView.visibility = View.GONE
// } else {
// holder.itemMainBinding.cardView.visibility = View.VISIBLE
// }
holder.name.text = remarks
holder.radio.isChecked = (position == configs.index)
holder.itemView.backgroundColor = Color.TRANSPARENT
holder.test_result.text = test_result
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
if (TextUtils.isEmpty(subid)) {
holder.subid.text = ""
holder.itemMainBinding.tvName.text = profile.remarks
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString().orEmpty()
if ((aff?.testDelayMillis ?: 0L) < 0L) {
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
} else {
holder.subid.text = "S"
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
}
if (guid == MmkvManager.getSelectServer()) {
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorAccent)
} else {
holder.itemMainBinding.layoutIndicator.setBackgroundResource(0)
}
holder.itemMainBinding.tvSubscription.text = MmkvManager.decodeSubscription(profile.subscriptionId)?.remarks ?: ""
var shareOptions = share_method.asList()
when (profile.configType) {
EConfigType.CUSTOM -> {
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
shareOptions = shareOptions.takeLast(1)
}
else -> {
holder.itemMainBinding.tvType.text = profile.configType.name
}
}
if (configType == AppConfig.EConfigType.Vmess) {
holder.type.text = "vmess"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
} else if (configType == AppConfig.EConfigType.Custom) {
holder.type.text = mActivity.getString(R.string.server_customize_config)
holder.statistics.text = ""//mActivity.getString(R.string.server_customize_config)
holder.layout_share.visibility = View.INVISIBLE
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
holder.type.text = "shadowsocks"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
} else if (configType == AppConfig.EConfigType.Socks) {
holder.type.text = "socks"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
}
// 替换服务器地址的'.'之后的内容为'***',例如将'1.23.45.67'替换为'1.23.45.***'
val modifiedServer = profile.server?.split('.')?.dropLast(1)?.joinToString(".", postfix = ".***")
val strState = "$modifiedServer : ${profile.serverPort}"
holder.layout_share.setOnClickListener {
mActivity.selector(null, share_method.asList()) { dialogInterface, i ->
holder.itemMainBinding.tvStatistics.text = strState
holder.itemMainBinding.layoutShare.setOnClickListener {
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
try {
when (i) {
0 -> {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
if (profile.configType == EConfigType.CUSTOM) {
shareFullContent(guid)
} else {
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
ivBinding.ivQcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
}
}
mActivity.alert {
customView {
linearLayout {
addView(iv)
}
}
}.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 -> {
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
else ->
mActivity.toast("else")
2 -> shareFullContent(guid)
else -> mActivity.toast("else")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}.show()
}
holder.layout_edit.setOnClickListener {
if (configType == AppConfig.EConfigType.Vmess) {
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Custom) {
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
mActivity.startActivity<Server3Activity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Socks) {
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable)
}
}
holder.layout_remove.setOnClickListener {
if (configs.index != position) {
if (AngConfigManager.removeServer(position) == 0) {
notifyItemRemoved(position)
updateSelectedItem(position)
}
}
}
holder.infoContainer.setOnClickListener {
if (changeable) {
AngConfigManager.setActiveServer(position)
holder.itemMainBinding.layoutEdit.setOnClickListener {
val intent = Intent().putExtra("guid", guid)
.putExtra("isRunning", isRunning)
if (profile.configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
} else {
mActivity.showCircle()
Utils.stopVService(mActivity)
AngConfigManager.setActiveServer(position)
Observable.timer(500, TimeUnit.MILLISECONDS)
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
}
}
holder.itemMainBinding.layoutRemove.setOnClickListener {
if (guid != MmkvManager.getSelectServer()) {
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
removeServer(guid, position)
}
.setNegativeButton(android.R.string.no) { _, _ ->
//do noting
}
.show()
} else {
removeServer(guid, position)
}
} else {
application.toast(R.string.toast_action_not_allowed)
}
}
holder.itemMainBinding.infoContainer.setOnClickListener {
val selected = MmkvManager.getSelectServer()
if (guid != selected) {
MmkvManager.setSelectServer(guid)
if (!TextUtils.isEmpty(selected)) {
notifyItemChanged(mActivity.mainViewModel.getPosition(selected.orEmpty()))
}
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
if (isRunning) {
Utils.stopVService(mActivity)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
mActivity.showCircle()
if (!Utils.startVService(mActivity)) {
mActivity.hideCircle()
}
V2RayServiceManager.startV2Ray(mActivity)
}
}
}
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()}")
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
when (viewType) {
VIEW_TYPE_ITEM ->
return MainViewHolder(parent.context.layoutInflater
.inflate(R.layout.item_recycler_main, parent, false))
else ->
return FooterViewHolder(parent.context.layoutInflater
.inflate(R.layout.item_recycler_footer, parent, false))
private fun shareFullContent(guid: String) {
if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
fun updateConfigList() {
configs = AngConfigManager.configs
notifyDataSetChanged()
private fun removeServer(guid: String, position: Int) {
mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position)
notifyItemRangeChanged(position, mActivity.mainViewModel.serversCache.size)
}
// fun updateSelectedItem() {
// updateSelectedItem(configs.index)
// }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return when (viewType) {
VIEW_TYPE_ITEM ->
MainViewHolder(ItemRecyclerMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
fun updateSelectedItem(pos: Int) {
//notifyItemChanged(pos)
notifyItemRangeChanged(pos, itemCount - pos)
else ->
FooterViewHolder(ItemRecyclerFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
}
override fun getItemViewType(position: Int): Int {
if (position == configs.vmess.count()) {
return VIEW_TYPE_FOOTER
return if (position == mActivity.mainViewModel.serversCache.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)
}
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
BaseViewHolder(itemFooterBinding.root)
override fun onItemClear() {
itemView.setBackgroundColor(0)
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true
}
override fun onItemMoveCompleted() {
// do nothing
}
override fun onItemDismiss(position: Int) {
if (configs.index != position) {
// mActivity.alert(R.string.del_config_comfirm) {
// positiveButton(android.R.string.ok) {
if (AngConfigManager.removeServer(position) == 0) {
notifyItemRemoved(position)
}
// }
// show()
// }
}
updateSelectedItem(position)
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
AngConfigManager.swapServer(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
//notifyDataSetChanged()
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
return true
}
}
@@ -1,41 +1,34 @@
package com.v2ray.ang.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.os.Bundle
import android.support.v7.widget.RecyclerView
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import com.dinuscxj.itemdecoration.LinearDividerItemDecoration
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.AppManagerUtil
import kotlinx.android.synthetic.main.activity_bypass_list.*
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.text.Collator
import java.util.*
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityBypassListBinding
import com.v2ray.ang.dto.AppInfo
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.util.AppManagerUtil
import com.v2ray.ang.util.MmkvManager.settingsStorage
import com.v2ray.ang.util.Utils
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.toast
import org.jetbrains.anko.uiThread
import java.net.URL
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.Collator
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 val binding by lazy {
ActivityBypassListBinding.inflate(layoutInflater)
}
private var adapter: PerAppProxyAdapter? = null
@@ -43,43 +36,40 @@ class PerAppProxyActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bypass_list)
setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
binding.recyclerView.addItemDecoration(dividerItemDecoration)
val dividerItemDecoration = LinearDividerItemDecoration(
this, LinearDividerItemDecoration.LINEAR_DIVIDER_VERTICAL)
recycler_view.addItemDecoration(dividerItemDecoration)
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null)
val blacklist = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
AppManagerUtil.rxLoadNetworkAppList(this)
.subscribeOn(Schedulers.io())
.map {
if (blacklist != null) {
it.forEach { one ->
if ((blacklist.contains(one.packageName))) {
one.isSelected = 1
} else {
one.isSelected = 0
}
.subscribeOn(Schedulers.io())
.map {
if (blacklist != null) {
it.forEach { one ->
if (blacklist.contains(one.packageName)) {
one.isSelected = 1
} else {
one.isSelected = 0
}
val comparator = object : Comparator<AppInfo> {
override fun compare(p1: AppInfo, p2: AppInfo): Int = when {
p1.isSelected > p2.isSelected -> -1
p1.isSelected == p2.isSelected -> 0
else -> 1
}
}
it.sortedWith(comparator)
} else {
val comparator = object : Comparator<AppInfo> {
val collator = Collator.getInstance()
override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName)
}
it.sortedWith(comparator)
}
val comparator = Comparator<AppInfo> { p1, p2 ->
when {
p1.isSelected > p2.isSelected -> -1
p1.isSelected == p2.isSelected -> 0
else -> 1
}
}
it.sortedWith(comparator)
} else {
val comparator = object : Comparator<AppInfo> {
val collator = Collator.getInstance()
override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName)
}
it.sortedWith(comparator)
}
}
// .map {
// val comparator = object : Comparator<AppInfo> {
// val collator = Collator.getInstance()
@@ -87,107 +77,125 @@ class PerAppProxyActivity : BaseActivity() {
// }
// it.sortedWith(comparator)
// }
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
appsAll = it
adapter = PerAppProxyAdapter(this, it, blacklist)
recycler_view.adapter = adapter
pb_waiting.visibility = View.GONE
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
appsAll = it
adapter = PerAppProxyAdapter(this, it, blacklist)
binding.recyclerView.adapter = adapter
binding.pbWaiting.visibility = View.GONE
}
/***
recycler_view.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()
dst = 0
} else if (dst < -20) {
header_view.show()
dst = 0
}
}
var dst = 0
val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 2
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
dst += dy
if (dst > threshold) {
header_view.hide()
dst = 0
} else if (dst < -20) {
header_view.show()
dst = 0
}
}
var hiding = false
fun View.hide() {
val target = -height.toFloat()
if (hiding || translationY == target) return
animate()
.translationY(target)
.setInterpolator(AccelerateInterpolator(2F))
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
hiding = false
}
})
hiding = true
}
var showing = false
fun View.show() {
val target = 0f
if (showing || translationY == target) return
animate()
.translationY(target)
.setInterpolator(DecelerateInterpolator(2F))
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
showing = false
}
})
showing = true
}
var hiding = false
fun View.hide() {
val target = -height.toFloat()
if (hiding || translationY == target) return
animate()
.translationY(target)
.setInterpolator(AccelerateInterpolator(2F))
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
hiding = false
}
})
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked ->
defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked)
hiding = true
}
switch_per_app_proxy.isChecked = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked ->
defaultDPreference.setPrefBoolean(PREF_BYPASS_APPS, isChecked)
var showing = false
fun View.show() {
val target = 0f
if (showing || translationY == target) return
animate()
.translationY(target)
.setInterpolator(DecelerateInterpolator(2F))
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
showing = false
}
switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false)
})
showing = true
}
})
***/
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
settingsStorage.encode(AppConfig.PREF_PER_APP_PROXY, isChecked)
}
binding.switchPerAppProxy.isChecked = settingsStorage.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
binding.switchBypassApps.setOnCheckedChangeListener { _, isChecked ->
settingsStorage.encode(AppConfig.PREF_BYPASS_APPS, isChecked)
}
binding.switchBypassApps.isChecked = settingsStorage.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
/***
et_search.setOnEditorActionListener { v, actionId, event ->
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)
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 apps = ArrayList<AppInfo>()
if (TextUtils.isEmpty(key)) {
appsAll?.forEach {
apps.add(it)
}
} else {
appsAll?.forEach {
if (it.appName.toUpperCase().indexOf(key) >= 0) {
apps.add(it)
}
}
}
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
recycler_view.adapter = adapter
adapter?.notifyDataSetChanged()
true
} else {
false
}
val key = v.text.toString().toUpperCase()
val apps = ArrayList<AppInfo>()
if (TextUtils.isEmpty(key)) {
appsAll?.forEach {
apps.add(it)
}
} else {
appsAll?.forEach {
if (it.appName.toUpperCase().indexOf(key) >= 0) {
apps.add(it)
}
}
}
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
recycler_view.adapter = adapter
adapter?.notifyDataSetChanged()
true
} else {
false
}
}
***/
}
override fun onPause() {
super.onPause()
adapter?.let {
defaultDPreference.setPrefStringSet(PREF_PER_APP_PROXY_SET, it.blacklist)
settingsStorage.encode(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_bypass_list, menu)
val searchItem = menu.findItem(R.id.search_view)
if (searchItem != null) {
val searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
filterProxyApp(newText.orEmpty())
return false
}
})
}
return super.onCreateOptionsMenu(menu)
}
@@ -197,59 +205,84 @@ class PerAppProxyActivity : BaseActivity() {
if (it.blacklist.containsAll(pkgNames)) {
it.apps.forEach {
val packageName = it.packageName
adapter?.blacklist!!.remove(packageName)
adapter?.blacklist?.remove(packageName)
}
} else {
it.apps.forEach {
val packageName = it.packageName
adapter?.blacklist!!.add(packageName)
adapter?.blacklist?.add(packageName)
}
}
it.notifyDataSetChanged()
true
} ?: false
R.id.select_proxy_app -> {
selectProxyApp()
true
}
R.id.import_proxy_app -> {
importProxyApp()
true
}
R.id.export_proxy_app -> {
exportProxyApp()
true
}
else -> super.onOptionsItemSelected(item)
}
private fun selectProxyApp() {
toast(R.string.msg_downloading_content)
val url = AppConfig.androidpackagenamelistUrl
doAsync {
val content = URL(url).readText()
uiThread {
Log.d("selectProxyApp", content)
selectProxyApp(content)
lifecycleScope.launch(Dispatchers.IO) {
val content = Utils.getUrlContext(url, 5000)
launch(Dispatchers.Main) {
Log.d(ANG_PACKAGE, content)
selectProxyApp(content, true)
toast(R.string.toast_success)
}
}
}
private fun selectProxyApp(content: String): Boolean {
private fun importProxyApp() {
val content = Utils.getClipboard(applicationContext)
if (TextUtils.isEmpty(content)) return
selectProxyApp(content, false)
toast(R.string.toast_success)
}
private fun exportProxyApp() {
var lst = binding.switchBypassApps.isChecked.toString()
adapter?.blacklist?.forEach block@{
lst = lst + System.getProperty("line.separator") + it
}
Utils.setClipboard(applicationContext, lst)
toast(R.string.toast_success)
}
private fun selectProxyApp(content: String, force: Boolean): Boolean {
try {
var proxyApps = content
if (TextUtils.isEmpty(content)) {
val assets = Utils.readTextFromAssets(v2RayApplication, "proxy_packagename.txt")
proxyApps = assets.lines().toString()
}
if (TextUtils.isEmpty(proxyApps)) {
return false
val proxyApps = if (TextUtils.isEmpty(content)) {
Utils.readTextFromAssets(v2RayApplication, "proxy_packagename.txt")
} else {
content
}
if (TextUtils.isEmpty(proxyApps)) return false
adapter?.blacklist!!.clear()
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)
if (proxyApps.indexOf(packageName) < 0) {
adapter?.blacklist!!.add(packageName)
Log.d(ANG_PACKAGE, packageName)
if (!inProxyApps(proxyApps, packageName, force)) {
adapter?.blacklist?.add(packageName)
println(packageName)
return@block
}
@@ -260,9 +293,9 @@ class PerAppProxyActivity : BaseActivity() {
adapter?.let {
it.apps.forEach block@{
val packageName = it.packageName
Log.d("selectProxyApp3", packageName)
if (proxyApps.indexOf(packageName) >= 0) {
adapter?.blacklist!!.add(packageName)
Log.d(ANG_PACKAGE, packageName)
if (inProxyApps(proxyApps, packageName, force)) {
adapter?.blacklist?.add(packageName)
println(packageName)
return@block
}
@@ -276,4 +309,37 @@ class PerAppProxyActivity : BaseActivity() {
}
return true
}
}
private fun inProxyApps(proxyApps: String, packageName: String, force: Boolean): Boolean {
if (force) {
if (packageName == "com.google.android.webview") return false
if (packageName.startsWith("com.google")) return true
}
return proxyApps.indexOf(packageName) >= 0
}
private fun filterProxyApp(content: String): Boolean {
val apps = ArrayList<AppInfo>()
val key = content.uppercase()
if (key.isNotEmpty()) {
appsAll?.forEach {
if (it.appName.uppercase().indexOf(key) >= 0
|| it.packageName.uppercase().indexOf(key) >= 0
) {
apps.add(it)
}
}
} else {
appsAll?.forEach {
apps.add(it)
}
}
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
binding.recyclerView.adapter = adapter
adapter?.notifyDataSetChanged()
return true
}
}
@@ -1,27 +1,22 @@
package com.v2ray.ang.ui
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
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 org.jetbrains.anko.image
import org.jetbrains.anko.layoutInflater
import org.jetbrains.anko.textColor
import java.util.*
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
RecyclerView.Adapter<PerAppProxyAdapter.BaseViewHolder>() {
RecyclerView.Adapter<PerAppProxyAdapter.BaseViewHolder>() {
companion object {
private const val VIEW_TYPE_HEADER = 0
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) {
@@ -38,15 +33,16 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
return when (viewType) {
VIEW_TYPE_HEADER -> {
val view = View(ctx)
view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 3)
view.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 0
)
BaseViewHolder(view)
}
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
// .inflate(R.layout.item_recycler_bypass_list, parent, false))
else -> AppViewHolder(ctx.layoutInflater
.inflate(R.layout.item_recycler_bypass_list, parent, false))
else -> AppViewHolder(ItemRecyclerBypassListBinding.inflate(LayoutInflater.from(ctx), parent, false))
}
}
@@ -55,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),
View.OnClickListener {
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.image = 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.textColor = Color.RED
itemBypassBinding.name.text = String.format("** %1s", appInfo.appName)
//name.textColor = Color.RED
} else {
name.text = appInfo.appName
name.textColor = Color.DKGRAY
itemBypassBinding.name.text = appInfo.appName
//name.textColor = Color.DKGRAY
}
itemView.setOnClickListener(this)
@@ -87,11 +78,11 @@ 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
}
}
}
}
}
@@ -0,0 +1,126 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import com.google.gson.Gson
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityRoutingEditBinding
import com.v2ray.ang.dto.RulesetItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.SettingsManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class RoutingEditActivity : BaseActivity() {
private val binding by lazy { ActivityRoutingEditBinding.inflate(layoutInflater) }
private val position by lazy { intent.getIntExtra("position", -1) }
private val outbound_tag: Array<out String> by lazy {
resources.getStringArray(R.array.outbound_tag)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
title = getString(R.string.routing_settings_rule_title)
val rulesetItem = SettingsManager.getRoutingRuleset(position)
if (rulesetItem != null) {
bindingServer(rulesetItem)
} else {
clearServer()
}
}
private fun bindingServer(rulesetItem: RulesetItem): Boolean {
binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks)
binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString())
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString())
binding.etPort.text = Utils.getEditable(rulesetItem.port)
binding.etProtocol.text = Utils.getEditable(rulesetItem.protocol?.joinToString())
binding.etNetwork.text = Utils.getEditable(rulesetItem.network)
val outbound = Utils.arrayFind(outbound_tag, rulesetItem.outboundTag)
binding.spOutboundTag.setSelection(outbound)
return true
}
private fun clearServer(): Boolean {
binding.etRemarks.text = null
binding.spOutboundTag.setSelection(0)
return true
}
private fun saveServer(): Boolean {
val rulesetItem = SettingsManager.getRoutingRuleset(position) ?: RulesetItem()
rulesetItem.remarks = binding.etRemarks.text.toString()
binding.etDomain.text.toString().let { rulesetItem.domain = if (it.isEmpty()) null else it.split(',') }
binding.etIp.text.toString().let { rulesetItem.ip = if (it.isEmpty()) null else it.split(',') }
binding.etProtocol.text.toString().let { rulesetItem.protocol = if (it.isEmpty()) null else it.split(',') }
binding.etPort.text.toString().let { rulesetItem.port = it.ifEmpty { null } }
binding.etNetwork.text.toString().let { rulesetItem.network = it.ifEmpty { null } }
rulesetItem.outboundTag = outbound_tag[binding.spOutboundTag.selectedItemPosition]
if (TextUtils.isEmpty(rulesetItem.remarks)) {
toast(R.string.sub_setting_remarks)
return false
}
SettingsManager.saveRoutingRuleset(position, rulesetItem)
toast(R.string.toast_success)
finish()
return true
}
private fun deleteServer(): Boolean {
if (position >= 0) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
SettingsManager.removeRoutingRuleset(position)
launch(Dispatchers.Main) {
finish()
}
}
}
.setNegativeButton(android.R.string.no) { _, _ ->
// do nothing
}
.show()
}
return true
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
val del_config = menu.findItem(R.id.del_config)
if (position < 0) {
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)
}
}

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