From cc0137ba6ae9e9fd919267bf19ee61ccd220e1f3 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 14 Nov 2017 11:05:19 +0800 Subject: [PATCH 01/49] Create CR.md --- CR.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CR.md diff --git a/CR.md b/CR.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/CR.md @@ -0,0 +1 @@ + From 6db1fef8c491de5e3751ad02eee09ebb7cd1d24b Mon Sep 17 00:00:00 2001 From: 2dust Date: Tue, 14 Nov 2017 14:23:17 +0800 Subject: [PATCH 02/49] support ws host --- CR.md | 31 + V2rayNG/app/build.gradle | 11 +- V2rayNG/app/src/main/AndroidManifest.xml | 26 +- .../vending/billing/IInAppBillingService.aidl | 144 +++ .../main/java/com/v2ray/ang/util/Base64.java | 570 ++++++++++ .../ang/util/Base64DecoderException.java | 32 + .../java/com/v2ray/ang/util/IabException.java | 43 + .../java/com/v2ray/ang/util/IabHelper.java | 979 ++++++++++++++++++ .../java/com/v2ray/ang/util/IabResult.java | 45 + .../java/com/v2ray/ang/util/Inventory.java | 91 ++ .../java/com/v2ray/ang/util/Purchase.java | 63 ++ .../java/com/v2ray/ang/util/Security.java | 119 +++ .../java/com/v2ray/ang/util/SkuDetails.java | 58 ++ .../main/kotlin/com/v2ray/ang/AppConfig.kt | 5 +- .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 11 +- .../com/v2ray/ang/receiver/WidgetProvider.kt | 91 ++ .../com/v2ray/ang/service/V2RayVpnService.kt | 70 +- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 88 +- .../com/v2ray/ang/ui/SettingsActivity.kt | 24 +- .../main/kotlin/com/v2ray/ang/util/Utils.kt | 31 + .../com/v2ray/ang/util/V2rayConfigUtil.kt | 77 +- .../src/main/res/drawable-xxhdpi/donate.png | Bin 0 -> 3237 bytes .../app/src/main/res/layout/widget_switch.xml | 29 + .../app/src/main/res/values-zh/strings.xml | 17 +- V2rayNG/app/src/main/res/values/strings.xml | 17 +- .../src/main/res/xml/app_widget_provider.xml | 6 + .../app/src/main/res/xml/pref_settings.xml | 14 + 27 files changed, 2585 insertions(+), 107 deletions(-) create mode 100644 CR.md create mode 100644 V2rayNG/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64DecoderException.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/IabException.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/IabResult.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/Inventory.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/Purchase.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/Security.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/SkuDetails.java create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt create mode 100644 V2rayNG/app/src/main/res/drawable-xxhdpi/donate.png create mode 100644 V2rayNG/app/src/main/res/layout/widget_switch.xml create mode 100644 V2rayNG/app/src/main/res/xml/app_widget_provider.xml diff --git a/CR.md b/CR.md new file mode 100644 index 0000000..fa66582 --- /dev/null +++ b/CR.md @@ -0,0 +1,31 @@ +v2rayNG尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务, v2rayNG会按照本隐私权政策的规定使用和披露您的个人信息。但v2rayNG将以高度的勤勉、 审慎义务对待这些信息。除本隐私权政策另有规定外,在未征得您事先许可的情况下,v2rayNG不 会将这些信息对外披露或向第三方提供。v2rayNG会不时更新本隐私权政策。 您在同意ECSHOP模板堂服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于v2rayNG服 务使用协议不可分割的一部分。 +适用范围 +1、在您注册v2rayNG帐号时,您根据v2rayNG要求提供的个人注册信息。 +2、在您使用v2rayNG网络服务,或访问v2rayNG平台网页时,ECSHOP模板堂自动接收并记录的您的浏览器和计算机上的信息,包括但不限于您的IP地址、浏 览器的类型、使用的语言、访问日期和时间、软硬件特征信息及您需求的网页记录等数据。 +3、v2rayNG通过合法途径从商业伙伴处取得的用户个人数据。 +您了解并同意,以下信息不适用本隐私权政策: +1、您在使用v2rayNG平台提供的搜索服务时输入的关键字信息。 +2、v2rayNG收集到的您在v2rayNG发布的有关信息数据,包括但不限于参与活动、成交信息及评价详情。 +3、违反法律规定或违反v2rayNG规则行为及v2rayNG已对您采取的措施。 +信息使用 +1、v2rayNG不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可, 或该第三方和v2rayNG(含v2rayNG关联公司)单独或共同为您提供服务,且在该服务结束后,其将被禁止访问 包括其以前能够访问的所有这些资料。 +2、v2rayNG亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。任何v2rayNG平台用户如从事 上述活动,一经发现,v2rayNG有权立即终止与该用户的服务协议。 +3、为服务用户的目的,v2rayNG可能通过使用您的个人信息,向您提供您感兴趣的信息,包括但不限于向您发出产品和服务信息, 或者与v2rayNG合作伙伴共享信息以便他们向您发送有关其产品和服务的信息(后者需要您的事先同意)。 +信息披露 +在如下情况下,v2rayNG将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息: +1、经您事先同意,向第三方披露。 +2、为提供您所要求的产品和服务,v2rayNG而必须和第三方分享您的个人信息。 +3、根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露。 +4、如您出现违反中国有关法律、法规或者v2rayNG服务协议或相关规则的情况,需要向第三方披露。 +5、如您是适格的知识产权投诉人并已提起投诉,应被投诉人要求,向被投诉人披露,以便双方处理可能的权利纠纷。 +6、在v2rayNG平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出信息披露请求的,v2rayNG有权决定向该用户提供其交易对方的联络方式等必要信息,以促成交易的完成或纠纷的解决。 +7、其它v2rayNG根据法律、法规或者网站政策认为合适的披露 。 +信息存储和交换 +v2rayNG收集的有关您的信息和资料将保存在v2rayNG及(或)其关联公司的服务器上,这些信息和资料可能 传送至您所在国家、地区或v2rayNG收集信息和资料所在地的境外并在境外被访问、存储和展示。 +Cookie的使用 +1、在您未拒绝接受cookies的情况下,v2rayNG会在您的计算机上设定或取用cookies ,以便您能登录或使用依赖于cookies的v2rayNG平台服务或功能。v2rayNG使用cookies可为您提供更加周到的个性化服务,包括推广服务。 +2、您有权选择接受或拒绝接受cookies。您可以通过修改浏览器设置的方式拒绝接受cookies。但如果您选择拒绝接受cookies,则您可能无法登录或使 用依赖于cookies的v2rayNG网络服务或功能。 +3、通过v2rayNG所设cookies所取得的有关信息,将适用本政策。 +信息安全 +1、v2rayNG帐号均有安全保护功能,请妥善保管您的用户名及密码信息。 v2rayNG将通过对用户密码进行加密等安全措施确保您的信息不丢失,不被滥用和变造。尽管有前述安全措施,但同时也请您注意在信息网络上不存在 “完善的安全措施”。 +2、在使用v2rayNG网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对方披露自己的个人信息,如联络方式或者邮政地址。请您妥善 保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息泄密,尤其是v2rayNG用户名及密码发生泄露,请您立即联络v2rayNG客服,以便v2rayNG采取相应措施。 diff --git a/V2rayNG/app/build.gradle b/V2rayNG/app/build.gradle index 939c19a..f922079 100644 --- a/V2rayNG/app/build.gradle +++ b/V2rayNG/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.v2ray.ang" minSdkVersion 17 targetSdkVersion Integer.parseInt("$targetSdkVer") - versionCode 35 - versionName "0.1.6" + versionCode 48 + versionName "0.2.1" } signingConfigs { @@ -35,17 +35,14 @@ android { minifyEnabled false zipAlignEnabled false shrinkResources false - signingConfig signingConfigs.debug + signingConfig signingConfigs.release // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { - buildConfigField "boolean", "LOG_DEBUG", "true" - - versionNameSuffix "-debug" minifyEnabled false zipAlignEnabled false shrinkResources false - signingConfig signingConfigs.debug + signingConfig signingConfigs.release } } diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index 3d8819c..600e190 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -4,8 +4,9 @@ - + + + + + @@ -38,6 +42,9 @@ android:name=".ui.PerAppProxyActivity" android:label="@string/title_pref_per_app_proxy" /> + - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl b/V2rayNG/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl new file mode 100644 index 0000000..2a492f7 --- /dev/null +++ b/V2rayNG/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl @@ -0,0 +1,144 @@ +/* + * 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); +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64.java new file mode 100644 index 0000000..eb69a06 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64.java @@ -0,0 +1,570 @@ +// 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: + *

+ * 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 + * http://iharder.net/xmlizable + * periodically to check for updates or to contribute improvements. + *

+ * + * @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. + * + *

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 source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @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 destination 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 source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination 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; + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64DecoderException.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64DecoderException.java new file mode 100644 index 0000000..b113e43 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64DecoderException.java @@ -0,0 +1,32 @@ +// 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; +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabException.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabException.java new file mode 100644 index 0000000..e632080 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabException.java @@ -0,0 +1,43 @@ +/* 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; } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java new file mode 100644 index 0000000..e58ae6e --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java @@ -0,0 +1,979 @@ +/* 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + */ + public 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) + */ + public 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 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 moreItemSkus, + List 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. + */ + public 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 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. + */ + public 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. + */ + public void onConsumeMultiFinished(List purchases, List 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 purchases = new ArrayList(); + 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 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 ownedSkus = ownedItems.getStringArrayList( + RESPONSE_INAPP_ITEM_LIST); + ArrayList purchaseDataList = ownedItems.getStringArrayList( + RESPONSE_INAPP_PURCHASE_DATA_LIST); + ArrayList 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 moreSkus) + throws RemoteException, JSONException { + logDebug("Querying SKU details."); + ArrayList skuList = new ArrayList(); + 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 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 purchases, + final OnConsumeFinishedListener singleListener, + final OnConsumeMultiFinishedListener multiListener) { + final Handler handler = new Handler(); + flagStartAsync("consume"); + (new Thread(new Runnable() { + public void run() { + final List results = new ArrayList(); + 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); + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabResult.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabResult.java new file mode 100644 index 0000000..0fbe5b5 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabResult.java @@ -0,0 +1,45 @@ +/* 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(); } +} + diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Inventory.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Inventory.java new file mode 100644 index 0000000..ae13e74 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Inventory.java @@ -0,0 +1,91 @@ +/* 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 mSkuMap = new HashMap(); + Map mPurchaseMap = new HashMap(); + + 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 getAllOwnedSkus() { + return new ArrayList(mPurchaseMap.keySet()); + } + + /** Returns a list of all owned product IDs of a given type */ + List getAllOwnedSkus(String itemType) { + List result = new ArrayList(); + for (Purchase p : mPurchaseMap.values()) { + if (p.getItemType().equals(itemType)) result.add(p.getSku()); + } + return result; + } + + /** Returns a list of all purchases. */ + List getAllPurchases() { + return new ArrayList(mPurchaseMap.values()); + } + + void addSkuDetails(SkuDetails d) { + mSkuMap.put(d.getSku(), d); + } + + void addPurchase(Purchase p) { + mPurchaseMap.put(p.getSku(), p); + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Purchase.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Purchase.java new file mode 100644 index 0000000..d5e5915 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Purchase.java @@ -0,0 +1,63 @@ +/* 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; } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Security.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Security.java new file mode 100644 index 0000000..50f02e3 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Security.java @@ -0,0 +1,119 @@ +/* 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; + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/SkuDetails.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/SkuDetails.java new file mode 100644 index 0000000..b15cd47 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/SkuDetails.java @@ -0,0 +1,58 @@ +/* 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; + } +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt index 915114e..7a347ca 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt @@ -5,12 +5,15 @@ package com.v2ray.ang * App Config Const */ object AppConfig { + const val ANG_PACKAGE = "com.v2ray.ang" const val ANG_CONFIG = "ang_config" const val PREF_CURR_CONFIG = "pref_v2ray_config" const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid" const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name" const val VMESS_PROTOCOL: String = "vmess://" - const val ACTION_STOP_V2RAY = "com.v2ray.ang.action.stop_v2ray" + 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" const val MSG_REGISTER_CLIENT = 1 const val MSG_STATE_RUNNING = 11 diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt index aa1da03..170feee 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt @@ -43,7 +43,8 @@ data class V2rayConfig(val port: Int, data class StreamSettingsBean(var network: String, var security: String, var tcpSettings: TcpsettingsBean?, - var kcpsettings: KcpsettingsBean?) { + var kcpsettings: KcpsettingsBean?, + var wssettings: WssettingsBean?) { data class TcpsettingsBean(var connectionReuse: Boolean = true, var header: HeaderBean = HeaderBean()) { @@ -62,6 +63,12 @@ data class V2rayConfig(val port: Int, var header: HeaderBean = HeaderBean()) { data class HeaderBean(var type: String = "none") } + + data class WssettingsBean(var connectionReuse: Boolean = true, + var path: String = "", + var headers: HeadersBean = HeadersBean()) { + data class HeadersBean(var Host: String = "") + } } } @@ -76,7 +83,7 @@ data class V2rayConfig(val port: Int, } } - data class DnsBean(val servers: List) + data class DnsBean(var servers: List) data class RoutingBean(val strategy: String, val settings: SettingsBean) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt new file mode 100644 index 0000000..68e0122 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt @@ -0,0 +1,91 @@ +package com.v2ray.ang.receiver + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.widget.RemoteViews +import com.v2ray.ang.R +import com.v2ray.ang.AppConfig +import com.v2ray.ang.service.V2RayVpnService +import com.v2ray.ang.util.AngConfigManager +import com.v2ray.ang.util.Utils +import org.jetbrains.anko.toast + +class WidgetProvider : AppWidgetProvider() { + /** + * 每次窗口小部件被更新都调用一次该方法 + */ + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + super.onUpdate(context, appWidgetManager, appWidgetIds) + + 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) + remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent) + + for (appWidgetId in appWidgetIds) { + appWidgetManager.updateAppWidget(appWidgetId, remoteViews) + } + } + + /** + * 接收窗口小部件点击时发送的广播 + */ + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) { + + if (Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")) { + val intent = Intent() + intent.action = AppConfig.BROADCAST_ACTION_SERVICE + intent.`package` = AppConfig.ANG_PACKAGE + intent.putExtra("key", AppConfig.MSG_STATE_STOP) + context.sendBroadcast(intent) + context.toast(R.string.toast_services_stop) + } else { + context.toast(R.string.toast_services_start) + if (AngConfigManager.genStoreV2rayConfig()) { + V2RayVpnService.startV2Ray(context) + } + } + } + } + + /** + * 每删除一次窗口小部件就调用一次 + */ + override fun onDeleted(context: Context, appWidgetIds: IntArray) { + super.onDeleted(context, appWidgetIds) + } + + /** + * 当最后一个该窗口小部件删除时调用该方法 + */ + override fun onDisabled(context: Context) { + super.onDisabled(context) + } + + /** + * 当该窗口小部件第一次添加到桌面时调用该方法 + */ + override fun onEnabled(context: Context) { + super.onEnabled(context) + } + + /** + * 当小部件大小改变时 + */ + override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) + } + + /** + * 当小部件从备份恢复时调用该方法 + */ + override fun onRestored(context: Context, oldWidgetIds: IntArray, newWidgetIds: IntArray) { + super.onRestored(context, oldWidgetIds, newWidgetIds) + } +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 1287d1a..3da9d22 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -10,13 +10,9 @@ import android.net.NetworkInfo import android.net.VpnService import android.os.* import android.support.v7.app.NotificationCompat -import android.util.Log import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork import com.orhanobut.logger.Logger import com.v2ray.ang.AppConfig -import com.v2ray.ang.AppConfig.ACTION_STOP_V2RAY -import com.v2ray.ang.AppConfig.PREF_CURR_CONFIG -import com.v2ray.ang.AppConfig.PREF_CURR_CONFIG_NAME import com.v2ray.ang.R import com.v2ray.ang.defaultDPreference import com.v2ray.ang.ui.MainActivity @@ -48,13 +44,6 @@ class V2RayVpnService : VpnService() { private val v2rayCallback = V2RayCallback() private var connectivitySubscription: Subscription? = null private lateinit var configContent: String - - private val stopV2RayReceiver = object : BroadcastReceiver() { - override fun onReceive(ctx: Context?, intent: Intent?) { - stopV2Ray() - } - } - private lateinit var mInterface: ParcelFileDescriptor val fd: Int get() = mInterface.fd @@ -87,7 +76,7 @@ class V2RayVpnService : VpnService() { } } - builder.setSession(defaultDPreference.getPrefString(PREF_CURR_CONFIG_NAME, "")) + builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) val dnsServers = Utils.getDnsServers() for (dns in dnsServers) @@ -117,7 +106,8 @@ class V2RayVpnService : VpnService() { // Create a new interface using the builder and save the parameters. mInterface = builder.establish() - Log.d("VPNService", "New interface: " + parameters) + //Logger.d("VPNService", "New interface: " + parameters) + //Logger.d(Libv2ray.checkVersionX()) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -144,9 +134,9 @@ class V2RayVpnService : VpnService() { private fun startV2ray() { if (!v2rayPoint.isRunning) { - registerReceiver(stopV2RayReceiver, IntentFilter(ACTION_STOP_V2RAY)) + registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)) - configContent = defaultDPreference.getPrefString(PREF_CURR_CONFIG, "") + configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") connectivitySubscription = ReactiveNetwork.observeNetworkConnectivity(this.applicationContext) .subscribeOn(Schedulers.io()) @@ -156,7 +146,7 @@ class V2RayVpnService : VpnService() { .observeOn(AndroidSchedulers.mainThread()) .subscribe { connectivity -> val state = connectivity.state - Log.e("ReactiveNetwork", state.toString()) + Logger.e("ReactiveNetwork", state.toString()) if (state == NetworkInfo.State.CONNECTED) { if (v2rayPoint.isRunning) { v2rayPoint.networkInterrupted() @@ -165,9 +155,12 @@ class V2RayVpnService : VpnService() { } v2rayPoint.callbacks = v2rayCallback - v2rayPoint.vpnSupportSet = v2rayCallback +// v2rayPoint.vpnSupportSet = v2rayCallback + v2rayPoint.setVpnSupportSet(v2rayCallback) + v2rayPoint.configureFile = "V2Ray_internal/ConfigureFileContent" v2rayPoint.configureFileContent = configContent + v2rayPoint.runLoop() } @@ -184,7 +177,8 @@ class V2RayVpnService : VpnService() { v2rayPoint.stopLoop() } - unregisterReceiver(stopV2RayReceiver) + unregisterReceiver(mMsgReceive) + connectivitySubscription?.let { it.unsubscribe() connectivitySubscription = null @@ -201,14 +195,17 @@ class V2RayVpnService : VpnService() { NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val stopV2RayIntent = Intent(ACTION_STOP_V2RAY) + 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 notification = NotificationCompat.Builder(applicationContext) .setSmallIcon(R.mipmap.ic_launcher) - .setContentTitle(defaultDPreference.getPrefString(PREF_CURR_CONFIG_NAME, "")) + .setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) .setContentText(getString(R.string.notification_action_more)) .setPriority(NotificationCompat.PRIORITY_MIN) .setContentIntent(contentPendingIntent) @@ -242,7 +239,7 @@ class V2RayVpnService : VpnService() { } override fun setup(s: String): Long { - Logger.d(s) + //Logger.d(s) try { this@V2RayVpnService.setup(s) return 0 @@ -253,19 +250,16 @@ class V2RayVpnService : VpnService() { } } - var mMsgSend: Messenger? = null - private var mMsgReceive = Messenger(ReceiveMessageHandler(this@V2RayVpnService)) + private var mMsgReceive = ReceiveMessageHandler(this@V2RayVpnService) - private class ReceiveMessageHandler(vpnService: V2RayVpnService) : Handler() { + private class ReceiveMessageHandler(vpnService: V2RayVpnService) : BroadcastReceiver() { internal var mReference: SoftReference = SoftReference(vpnService) - override fun handleMessage(msg: Message) { - super.handleMessage(msg) + override fun onReceive(ctx: Context?, intent: Intent?) { val vpnService = mReference.get() - when (msg.what) { + when (intent?.getIntExtra("key", 0)) { AppConfig.MSG_REGISTER_CLIENT -> { - Log.e("ReceiveMessageHandler", msg.data.get("key").toString()) - vpnService?.mMsgSend = msg.replyTo + //Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString()) val isRunning = vpnService?.v2rayPoint!!.isRunning && VpnService.prepare(vpnService) == null @@ -276,7 +270,7 @@ class V2RayVpnService : VpnService() { } } AppConfig.MSG_UNREGISTER_CLIENT -> { - vpnService?.mMsgSend = null +// vpnService?.mMsgSend = null } AppConfig.MSG_STATE_START -> { //nothing to do @@ -288,19 +282,13 @@ class V2RayVpnService : VpnService() { } } - override fun onBind(intent: Intent?): IBinder { - return mMsgReceive.binder - } - fun sendMsg(what: Int, content: String) { try { - val msg = Message.obtain() -// msg.replyTo = mMsgReceive - msg.what = what - val bundle = Bundle() - bundle.putString("key", content) - msg.data = bundle - mMsgSend?.send(msg) + val intent = Intent() + intent.action = AppConfig.BROADCAST_ACTION_ACTIVITY + intent.`package` = AppConfig.ANG_PACKAGE + intent.putExtra("key", what) + sendBroadcast(intent) } catch (e: Exception) { e.printStackTrace() } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 0248647..6d3bb12 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -1,9 +1,7 @@ package com.v2ray.ang.ui import android.Manifest -import android.content.ComponentName -import android.content.Intent -import android.content.ServiceConnection +import android.content.* import android.net.VpnService import android.os.* import android.support.v7.widget.LinearLayoutManager @@ -19,9 +17,12 @@ import org.jetbrains.anko.imageResource import org.jetbrains.anko.startActivity import org.jetbrains.anko.toast import android.os.Bundle +import android.view.KeyEvent import com.v2ray.ang.AppConfig import org.jetbrains.anko.startActivityForResult import java.lang.ref.SoftReference +import android.view.KeyEvent.KEYCODE_BACK + class MainActivity : BaseActivity() { companion object { @@ -63,23 +64,30 @@ class MainActivity : BaseActivity() { } fun startV2Ray() { + toast(R.string.toast_services_start) if (AngConfigManager.genStoreV2rayConfig()) { - toast(R.string.toast_services_start) V2RayVpnService.startV2Ray(this) } } override fun onStart() { super.onStart() - val intent = Intent(this.applicationContext, V2RayVpnService::class.java) - intent.`package` = "com.v2ray.ang" - bindService(intent, mConnection, BIND_AUTO_CREATE) + fabChecked = 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)) + sendMsg(AppConfig.MSG_REGISTER_CLIENT, "") } override fun onStop() { super.onStop() - - unbindService(mConnection) +// unbindService(mConnection) + unregisterReceiver(mMsgReceive) + mMsgReceive = null } public override fun onResume() { @@ -87,6 +95,10 @@ class MainActivity : BaseActivity() { adapter.updateConfigList() } + public override fun onPause() { + super.onPause() + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { @@ -185,15 +197,22 @@ class MainActivity : BaseActivity() { } } - private var mMsgReceive: Messenger? = null +// val mConnection = object : ServiceConnection { +// override fun onServiceDisconnected(name: ComponentName?) { +// } +// +// override fun onServiceConnected(name: ComponentName?, service: IBinder?) { +// sendMsg(AppConfig.MSG_REGISTER_CLIENT, "") +// } +// } - private class ReceiveMessageHandler(activity: MainActivity) : Handler() { - internal var mReference: SoftReference = SoftReference(activity) + private var mMsgReceive: BroadcastReceiver? = null - override fun handleMessage(msg: Message) { - super.handleMessage(msg) + private class ReceiveMessageHandler(activity: MainActivity) : BroadcastReceiver() { + internal var mReference: SoftReference = SoftReference(activity) + override fun onReceive(ctx: Context?, intent: Intent?) { val activity = mReference.get() - when (msg.what) { + when (intent?.getIntExtra("key", 0)) { AppConfig.MSG_STATE_RUNNING -> { activity?.fabChecked = true } @@ -201,12 +220,12 @@ class MainActivity : BaseActivity() { activity?.fabChecked = false } AppConfig.MSG_STATE_START_SUCCESS -> { - activity?.fabChecked = true activity?.toast(R.string.toast_services_success) + activity?.fabChecked = true } AppConfig.MSG_STATE_START_FAILURE -> { - activity?.fabChecked = false activity?.toast(R.string.toast_services_failure) + activity?.fabChecked = false } AppConfig.MSG_STATE_STOP_SUCCESS -> { activity?.fabChecked = false @@ -215,34 +234,23 @@ class MainActivity : BaseActivity() { } } - private var mMsgSend: Messenger? = null - val mConnection = object : ServiceConnection { - override fun onServiceDisconnected(name: ComponentName?) { - mMsgSend = null - mMsgReceive = null - } - - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - mMsgSend = Messenger(service) - mMsgReceive = Messenger(ReceiveMessageHandler(this@MainActivity)) - - sendMsg(AppConfig.MSG_REGISTER_CLIENT, "") - } - } - fun sendMsg(what: Int, content: String) { try { - val msg = Message.obtain() - msg.replyTo = mMsgReceive - msg.what = what - val bundle = Bundle() - bundle.putString("key", content) - msg.data = bundle - mMsgSend?.send(msg) + val intent = Intent() + intent.action = AppConfig.BROADCAST_ACTION_SERVICE + intent.`package` = AppConfig.ANG_PACKAGE + intent.putExtra("key", what) + sendBroadcast(intent) } catch (e: Exception) { e.printStackTrace() } } - + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK) { + moveTaskToBack(false) + return true + } + return super.onKeyDown(keyCode, event) + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index cc4e2a6..373d25a 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -5,10 +5,12 @@ import android.content.SharedPreferences import android.net.Uri import android.os.Bundle import android.preference.CheckBoxPreference +import android.preference.EditTextPreference import android.preference.Preference import android.preference.PreferenceFragment import android.support.v7.app.AppCompatActivity import com.v2ray.ang.BuildConfig +import com.v2ray.ang.InappBuyActivity import com.v2ray.ang.R import com.v2ray.ang.defaultDPreference import com.v2ray.ang.extension.onClick @@ -22,6 +24,9 @@ class SettingsActivity : BaseActivity() { const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland" // const val PREF_START_ON_BOOT = "pref_start_on_boot" const val PREF_PER_APP_PROXY = "pref_per_app_proxy" + const val PREF_REMOTE_DNS = "pref_remote_dns" + + const val PREF_DONATE = "pref_donate" const val PREF_LICENSES = "pref_licenses" const val PREF_FEEDBACK = "pref_feedback" const val PREF_VERSION = "pref_version" @@ -38,6 +43,9 @@ class SettingsActivity : BaseActivity() { class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference } // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } + val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } + + val donate: Preference by lazy { findPreference(PREF_DONATE) } val licenses: Preference by lazy { findPreference(PREF_LICENSES) } val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } val version: Preference by lazy { findPreference(PREF_VERSION) } @@ -46,6 +54,10 @@ class SettingsActivity : BaseActivity() { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.pref_settings) + donate.onClick { + donate() + } + licenses.onClick { val fragment = LicensesDialogFragment.Builder(act) .setNotices(R.raw.licenses) @@ -55,7 +67,7 @@ class SettingsActivity : BaseActivity() { } feedback.onClick { - openUri("https://github.com/d4boy/v2rayNG/issues") + openUri("https://github.com/v2ray/v2rayNG/issues") } perAppProxy.setOnPreferenceClickListener { @@ -64,6 +76,11 @@ class SettingsActivity : BaseActivity() { false } + remoteDns.setOnPreferenceChangeListener { preference, any -> + remoteDns.summary = any as String + true + } + version.summary = BuildConfig.VERSION_NAME } @@ -71,6 +88,7 @@ class SettingsActivity : BaseActivity() { super.onStart() perAppProxy.isChecked = defaultSharedPreferences.getBoolean(PREF_PER_APP_PROXY, false) + remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "") defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this) } @@ -94,6 +112,10 @@ class SettingsActivity : BaseActivity() { val uri = Uri.parse(uriString) startActivity(Intent(Intent.ACTION_VIEW, uri)) } + + private fun donate() { + startActivity() + } } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 5db969b..6327d90 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -11,6 +11,10 @@ import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.EncodeHintType import java.util.* import kotlin.collections.HashMap +import android.app.ActivityManager +import com.orhanobut.logger.Logger +import java.util.logging.LogManager + object Utils { @@ -171,6 +175,33 @@ object Utils { return false } } + + /** + * 判断服务是否后台运行 + + * @param context + * * Context + * * + * @param className + * * 判断的服务名字 + * * + * @return true 在运行 false 不在运行 + */ + fun isServiceRun(mContext: Context, className: String): Boolean { + var isRun = false + val activityManager = mContext + .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val serviceList = activityManager + .getRunningServices(900) + val size = serviceList.size + for (i in 0..size - 1) { + if (serviceList[i].service.className == className) { + isRun = true + break + } + } + return isRun + } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 9c7da26..e531805 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -5,7 +5,11 @@ import com.google.gson.Gson import com.v2ray.ang.AngApplication import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.V2rayConfig +import com.v2ray.ang.ui.SettingsActivity import org.json.JSONObject +import org.json.JSONArray +import java.util.LinkedHashSet + object V2rayConfigUtil { val lib2rayObj: JSONObject by lazy { @@ -52,10 +56,10 @@ object V2rayConfigUtil { } val requestObj: JSONObject by lazy { - JSONObject("""{"version":"1.1","method":"GET","path":["/"],"headers":{"Host":[""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") + JSONObject("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") } val responseObj: JSONObject by lazy { - JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","application/x-msdownload","text/html","application/x-shockwave-flash"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") + JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") } data class Result(var status: Boolean, var content: String) @@ -92,6 +96,9 @@ object V2rayConfigUtil { //routing routing(config, v2rayConfig) + //dns + customDns(config, v2rayConfig, app) + //增加lib2ray val finalConfig = addLib2ray(v2rayConfig) @@ -141,7 +148,7 @@ object V2rayConfigUtil { * 远程服务器底层传输配置 */ private fun boundStreamSettings(config: AngConfig): V2rayConfig.OutboundBean.StreamSettingsBean { - val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null) + val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null) try { //远程服务器底层传输配置 streamSettings.network = config.vmess[config.index].network @@ -163,6 +170,17 @@ object V2rayConfigUtil { streamSettings.kcpsettings = kcpsettings } "ws" -> { + val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean() + wssettings.connectionReuse = true + val lstParameter = config.vmess[config.index].requestHost.split(";") + if (lstParameter.size > 0) { + wssettings.path = lstParameter.get(0) + } + if (lstParameter.size > 1) { + wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean.HeadersBean() + wssettings.headers.Host = lstParameter.get(1) + } + streamSettings.wssettings = wssettings } else -> { //tcp带http伪装 @@ -173,10 +191,15 @@ object V2rayConfigUtil { tcpSettings.header.type = config.vmess[config.index].headerType if (requestObj.has("headers") - || requestObj.optJSONObject("headers").has("Host")) { + || requestObj.optJSONObject("headers").has("Pragma")) { + val arrHost = JSONArray() + config.vmess[config.index].requestHost + .split(",") + .forEach { + arrHost.put(it) + } requestObj.optJSONObject("headers") - .optJSONArray("Host") - .put(config.vmess[config.index].requestHost) + .put("Host", arrHost) tcpSettings.header.request = requestObj tcpSettings.header.response = responseObj } @@ -215,6 +238,20 @@ object V2rayConfigUtil { return true } + /** + * Custom Dns + */ + fun customDns(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { + try { + v2rayConfig.dns.servers = getRemoteDnsServers(app) + } catch (e: Exception) { + e.printStackTrace() + return false + } + return true + } + + /** * 增加lib2ray */ @@ -229,4 +266,32 @@ object V2rayConfigUtil { return "" } } + + /** + * get remote dns servers from preference + */ + fun getRemoteDnsServers(app: AngApplication): List { + val ret = ArrayList() + val remoteDns = app.defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, "") + if (!TextUtils.isEmpty(remoteDns)) { + remoteDns + .split(",") + .forEach { + if (Utils.isIpAddress(it)) { + ret.add(it) + } + } + } + + if (!ret.contains("8.8.8.8")) { + ret.add("8.8.8.8") + } + if (!ret.contains("8.8.4.4")) { + ret.add("8.8.4.4") + } + if (!ret.contains("localhost")) { + ret.add("localhost") + } + return ret + } } diff --git a/V2rayNG/app/src/main/res/drawable-xxhdpi/donate.png b/V2rayNG/app/src/main/res/drawable-xxhdpi/donate.png new file mode 100644 index 0000000000000000000000000000000000000000..8825c5320f2146691887b523c07b81874609d6db GIT binary patch literal 3237 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 literal 0 HcmV?d00001 diff --git a/V2rayNG/app/src/main/res/layout/widget_switch.xml b/V2rayNG/app/src/main/res/layout/widget_switch.xml new file mode 100644 index 0000000..aa5d03e --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/widget_switch.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-zh/strings.xml b/V2rayNG/app/src/main/res/values-zh/strings.xml index 7230a9b..59e5163 100644 --- a/V2rayNG/app/src/main/res/values-zh/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh/strings.xml @@ -8,6 +8,7 @@ 无法取得权限 点击了解更多 启动服务中 + 关闭中 启动服务成功 启动服务失败 @@ -29,7 +30,7 @@ 传输协议(network) 功能设置(不清楚则保持默认值) 伪装类型(type) - Tcp伪装域名(host) + 伪装域名/ws path/ws(path+host)分号(;)隔开 底层传输安全 成功 失败 @@ -46,6 +47,7 @@ 设置 关于 + 进阶设置 绕过大陆地址 绕过大陆地址 @@ -53,11 +55,24 @@ 分应用代理 分应用代理仅支持 Android 5.0 Lollipop 及更高 + 捐赠 + 支持开发者,并激活进阶功能 + 陆续增加一些试验性的进阶功能 + + 远程DNS + 远程DNS + 反馈 反馈改进或漏洞至 GitHub 版本 + 初始化错误: + 无法查询到项目 + 错误: + 错误,真实性验证失败. + 感谢您的捐赠! + 二维码 导出至剪贴板 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 03d41d1..a090b85 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -8,6 +8,7 @@ Unable to obtain the permission click for more Start Services + Stop Services Start Services Success Start Services Failure @@ -29,7 +30,7 @@ network more function head type - request host + request host/ws path/ws(path;host) tls Success Failure @@ -49,6 +50,7 @@ Settings About + Advanced Settings Bypass mainland Bypass mainland @@ -56,11 +58,24 @@ Per-app proxy Per-app proxy mode only support Android 5.0 Lollipop or higher + Donate + Donate developer, and activate advanced features + Add some experimental advanced features + + Remote DNS + Remote DNS + Feedback Feedback enhancements or bugs to GitHub Version + Error Setup: + Error querying inventory + Error Purchase: + Error Purchase,Authenticity verification failed. + Thank you for your donation! + QRcode Export to clipboard diff --git a/V2rayNG/app/src/main/res/xml/app_widget_provider.xml b/V2rayNG/app/src/main/res/xml/app_widget_provider.xml new file mode 100644 index 0000000..f457545 --- /dev/null +++ b/V2rayNG/app/src/main/res/xml/app_widget_provider.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index bf704cc..6977d4f 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -11,6 +11,20 @@ android:summary="@string/summary_pref_per_app_proxy" android:title="@string/title_pref_per_app_proxy" /> + + + + + + + From e9c1cf0d27ba65e9e261041c04dd2c63b8c93faa Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 22 Nov 2017 08:48:53 +0800 Subject: [PATCH 03/49] Update CR.md --- CR.md | 58 +++++++++++++++++++++++----------------------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/CR.md b/CR.md index b5ff4b3..3f06c89 100644 --- a/CR.md +++ b/CR.md @@ -1,35 +1,23 @@ -<<<<<<< HEAD -v2rayNG尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务, v2rayNG会按照本隐私权政策的规定使用和披露您的个人信息。但v2rayNG将以高度的勤勉、 审慎义务对待这些信息。除本隐私权政策另有规定外,在未征得您事先许可的情况下,v2rayNG不 会将这些信息对外披露或向第三方提供。v2rayNG会不时更新本隐私权政策。 您在同意ECSHOP模板堂服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于v2rayNG服 务使用协议不可分割的一部分。 -适用范围 -1、在您注册v2rayNG帐号时,您根据v2rayNG要求提供的个人注册信息。 -2、在您使用v2rayNG网络服务,或访问v2rayNG平台网页时,ECSHOP模板堂自动接收并记录的您的浏览器和计算机上的信息,包括但不限于您的IP地址、浏 览器的类型、使用的语言、访问日期和时间、软硬件特征信息及您需求的网页记录等数据。 -3、v2rayNG通过合法途径从商业伙伴处取得的用户个人数据。 -您了解并同意,以下信息不适用本隐私权政策: -1、您在使用v2rayNG平台提供的搜索服务时输入的关键字信息。 -2、v2rayNG收集到的您在v2rayNG发布的有关信息数据,包括但不限于参与活动、成交信息及评价详情。 -3、违反法律规定或违反v2rayNG规则行为及v2rayNG已对您采取的措施。 -信息使用 -1、v2rayNG不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可, 或该第三方和v2rayNG(含v2rayNG关联公司)单独或共同为您提供服务,且在该服务结束后,其将被禁止访问 包括其以前能够访问的所有这些资料。 -2、v2rayNG亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。任何v2rayNG平台用户如从事 上述活动,一经发现,v2rayNG有权立即终止与该用户的服务协议。 -3、为服务用户的目的,v2rayNG可能通过使用您的个人信息,向您提供您感兴趣的信息,包括但不限于向您发出产品和服务信息, 或者与v2rayNG合作伙伴共享信息以便他们向您发送有关其产品和服务的信息(后者需要您的事先同意)。 -信息披露 -在如下情况下,v2rayNG将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息: -1、经您事先同意,向第三方披露。 -2、为提供您所要求的产品和服务,v2rayNG而必须和第三方分享您的个人信息。 -3、根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露。 -4、如您出现违反中国有关法律、法规或者v2rayNG服务协议或相关规则的情况,需要向第三方披露。 -5、如您是适格的知识产权投诉人并已提起投诉,应被投诉人要求,向被投诉人披露,以便双方处理可能的权利纠纷。 -6、在v2rayNG平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出信息披露请求的,v2rayNG有权决定向该用户提供其交易对方的联络方式等必要信息,以促成交易的完成或纠纷的解决。 -7、其它v2rayNG根据法律、法规或者网站政策认为合适的披露 。 -信息存储和交换 -v2rayNG收集的有关您的信息和资料将保存在v2rayNG及(或)其关联公司的服务器上,这些信息和资料可能 传送至您所在国家、地区或v2rayNG收集信息和资料所在地的境外并在境外被访问、存储和展示。 -Cookie的使用 -1、在您未拒绝接受cookies的情况下,v2rayNG会在您的计算机上设定或取用cookies ,以便您能登录或使用依赖于cookies的v2rayNG平台服务或功能。v2rayNG使用cookies可为您提供更加周到的个性化服务,包括推广服务。 -2、您有权选择接受或拒绝接受cookies。您可以通过修改浏览器设置的方式拒绝接受cookies。但如果您选择拒绝接受cookies,则您可能无法登录或使 用依赖于cookies的v2rayNG网络服务或功能。 -3、通过v2rayNG所设cookies所取得的有关信息,将适用本政策。 -信息安全 -1、v2rayNG帐号均有安全保护功能,请妥善保管您的用户名及密码信息。 v2rayNG将通过对用户密码进行加密等安全措施确保您的信息不丢失,不被滥用和变造。尽管有前述安全措施,但同时也请您注意在信息网络上不存在 “完善的安全措施”。 -2、在使用v2rayNG网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对方披露自己的个人信息,如联络方式或者邮政地址。请您妥善 保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息泄密,尤其是v2rayNG用户名及密码发生泄露,请您立即联络v2rayNG客服,以便v2rayNG采取相应措施。 -======= - ->>>>>>> origin/master +v2rayNG 隐私条款 +最后更新 2017-11-22 + +v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** + +1. 信息收集 +v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。 + +v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。 + +当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。** + +2. 信息共享 +我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。 + +3. 信息存留 +除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。 + +4. 信息泄露 +我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。** + +5. 条款修改 +我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。** From 23c05e2044bec0073dc1d94fa0d73225261b0c66 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 22 Nov 2017 08:52:00 +0800 Subject: [PATCH 04/49] Update CR.md --- CR.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CR.md b/CR.md index 3f06c89..914b839 100644 --- a/CR.md +++ b/CR.md @@ -3,21 +3,21 @@ v2rayNG 隐私条款 v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** -1. 信息收集 +1. 信息收集 v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。 v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。 当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。** -2. 信息共享 +2. 信息共享 我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。 3. 信息存留 除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。 -4. 信息泄露 +4. 信息泄露 我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。** -5. 条款修改 +5. 条款修改 我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。** From c5dcad080930160701335405d207758d6a1a1efa Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 22 Nov 2017 08:52:32 +0800 Subject: [PATCH 05/49] Update CR.md --- CR.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CR.md b/CR.md index 914b839..5b65a90 100644 --- a/CR.md +++ b/CR.md @@ -1,5 +1,5 @@ -v2rayNG 隐私条款 -最后更新 2017-11-22 +v2rayNG 隐私条款 +最后更新 2017-11-22 v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** From b7502b84cd6142f2a979bb1d2fd9666f132d7a81 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 22 Nov 2017 08:58:04 +0800 Subject: [PATCH 06/49] Update CR.md --- CR.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CR.md b/CR.md index 5b65a90..2b2a3d3 100644 --- a/CR.md +++ b/CR.md @@ -3,7 +3,8 @@ v2rayNG 隐私条款 v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** -1. 信息收集 +1. 信息收集 + v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。 v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。 From 6653bf2dec514a930ce0c039c85a7da9cdee92c2 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 22 Nov 2017 09:01:52 +0800 Subject: [PATCH 07/49] Update CR.md --- CR.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/CR.md b/CR.md index 2b2a3d3..97e9b8b 100644 --- a/CR.md +++ b/CR.md @@ -1,24 +1,23 @@ -v2rayNG 隐私条款 -最后更新 2017-11-22 +v2rayNG 隐私条款 +最后更新 2017-11-22 -v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** + v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** 1. 信息收集 + v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。 -v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。 + v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。 -v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。 + 当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。** -当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。** - -2. 信息共享 -我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。 +2. 信息共享 + 我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。 3. 信息存留 -除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。 + 除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。 -4. 信息泄露 -我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。** +4. 信息泄露 + 我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。** -5. 条款修改 -我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。** +5. 条款修改 + 我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。** From 5e0f21bd5f2f831e4845e8a59aa60c8b57acaa22 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 22 Nov 2017 09:03:55 +0800 Subject: [PATCH 08/49] Update CR.md --- CR.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CR.md b/CR.md index 97e9b8b..dc3773c 100644 --- a/CR.md +++ b/CR.md @@ -1,9 +1,10 @@ v2rayNG 隐私条款 最后更新 2017-11-22 - v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** +v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** 1. 信息收集 + v2rayNG 软件自身不会发送任何信息到开发者,但是您下载软件的应用市场(如 Google Play)可能会收集关于应用运行状态的相关信息并提供给 v2rayNG 开发者。有关这些信息,请阅读您使用的应用市场所提供的隐私条款。 v2rayNG 软件中可能包含需要通过 IAP 支付解锁的功能,您的支付信息将由相关的 IAP 渠道进行处理,而我们对支付信息没有访问权。 @@ -11,13 +12,17 @@ v2rayNG 隐私条款 当您向 v2rayNG 开发者反馈软件运行中的错误时,开发者可能会要求您提供软件以及系统的日志以帮助确认问题的原因。因日志中可能包括敏感信息,此类信息只能由您自己操作发送。**我们不对任何传输服务的安全性和隐私性做任何明示或暗示的担保,请您在传送相关信息时选择可以您自身可以接受的方式。** 2. 信息共享 + 我们不会向任何第三方出售收集到的用户数据。我们可能向外部开发者提供信息以协助软件的开发,但是在提供信息之前我们会传达相关保密义务并确定其可以遵守。 3. 信息存留 + 除非有相关法律规定,我们会在 30 天内清除不需要继续使用的用户数据,或将统计数据整合为无法识别单个用户的综合报告。 4. 信息泄露 + 我们会使用合理的技术和安全手段尽力保护用户的数据,但是无法保证数据的绝对安全。如果我们确认数据发生了泄露,我们会在 7 天内通过可用的渠道通知用户。**您同意不向我们追责任何因不可抗力而造成的损失。** 5. 条款修改 + 我们保留修改这份隐私条款的权利,但是会确保在更新条款前至少 30 天通过我们的可用渠道和应用内提示来通知用户。**在新条款生效后继续使用软件即表示您同意修改后的隐私条款。** From 490fccccf6aea94102f8109be845da32fdbafba4 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Wed, 22 Nov 2017 09:04:45 +0800 Subject: [PATCH 09/49] Update CR.md --- CR.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CR.md b/CR.md index dc3773c..0ec148b 100644 --- a/CR.md +++ b/CR.md @@ -1,4 +1,5 @@ v2rayNG 隐私条款 + 最后更新 2017-11-22 v2rayNG 尊重并保护所有用户的个人隐私权,为此我们向大众公开这份隐私条款。**您使用 v2rayNG 即代表您以阅读并同意了这份条款,如果您不同意这份条款请立即停止使用并卸载 v2rayNG。** From 9bfb704142d8c467ddea3be38531f95818037077 Mon Sep 17 00:00:00 2001 From: 2dust Date: Thu, 23 Nov 2017 08:20:06 +0800 Subject: [PATCH 10/49] add quick setting --- .gitignore | 1 + V2rayNG/app/build.gradle | 6 +- V2rayNG/app/src/main/AndroidManifest.xml | 19 + .../java/com/v2ray/ang/util/LogRecorder.java | 540 ++++++++++++++++++ .../kotlin/com/v2ray/ang/AngApplication.kt | 6 - .../main/kotlin/com/v2ray/ang/AppConfig.kt | 2 + .../kotlin/com/v2ray/ang/dto/AngConfig.kt | 3 +- .../main/kotlin/com/v2ray/ang/dto/AppInfo.kt | 8 + .../kotlin/com/v2ray/ang/extension/_Ext.kt | 20 + .../ang/receiver/NetWorkStateReceiver.kt | 70 +++ .../com/v2ray/ang/receiver/WidgetProvider.kt | 8 +- .../com/v2ray/ang/service/QSTileService.kt | 98 ++++ .../com/v2ray/ang/service/V2RayVpnService.kt | 101 ++-- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 195 +++++-- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 20 +- .../com/v2ray/ang/ui/PerAppProxyActivity.kt | 57 +- .../com/v2ray/ang/ui/PerAppProxyAdapter.kt | 10 +- .../com/v2ray/ang/ui/Server2Activity.kt | 140 +++++ .../com/v2ray/ang/ui/SettingsActivity.kt | 7 +- .../com/v2ray/ang/util/AngConfigManager.kt | 41 ++ .../com/v2ray/ang/util/AppManagerUtil.kt | 6 +- .../kotlin/com/v2ray/ang/util/MessageUtil.kt | 30 + .../main/kotlin/com/v2ray/ang/util/Utils.kt | 18 + .../com/v2ray/ang/util/V2rayConfigUtil.kt | 144 ++++- .../src/main/res/drawable/ic_start_busy.xml | 13 + .../main/res/drawable/ic_start_connected.xml | 13 + .../src/main/res/drawable/ic_start_idle.xml | 24 + .../main/res/layout/activity_bypass_list.xml | 30 +- .../src/main/res/layout/activity_server2.xml | 73 +++ V2rayNG/app/src/main/res/menu/menu_main.xml | 13 + .../app/src/main/res/values-zh/strings.xml | 16 +- V2rayNG/app/src/main/res/values/dimens.xml | 2 +- V2rayNG/app/src/main/res/values/strings.xml | 22 +- V2rayNG/build.gradle | 5 +- V2rayNG/gradle.properties | 14 +- 35 files changed, 1619 insertions(+), 156 deletions(-) create mode 100644 .gitignore create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/LogRecorder.java create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AppInfo.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MessageUtil.kt create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_busy.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_connected.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_idle.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_server2.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0202f9c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +V2rayNG/app/src/main/res/layout/activity_inapp_buy.xml \ No newline at end of file diff --git a/V2rayNG/app/build.gradle b/V2rayNG/app/build.gradle index f922079..79b9102 100644 --- a/V2rayNG/app/build.gradle +++ b/V2rayNG/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.v2ray.ang" minSdkVersion 17 targetSdkVersion Integer.parseInt("$targetSdkVer") - versionCode 48 - versionName "0.2.1" + versionCode 52 + versionName "0.2.4" } signingConfigs { @@ -93,7 +93,7 @@ dependencies { compile "org.jetbrains.anko:anko-design:$ankoVersion" compile 'com.google.code.gson:gson:2.7' - compile 'com.github.pwittchen:reactivenetwork:0.10.0' + compile 'com.github.pwittchen:reactivenetwork:0.12.2' compile 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar' compile 'de.psdev.licensesdialog:licensesdialog:1.8.1' compile 'com.dinuscxj:recycleritemdecoration:1.0.0' diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index 600e190..ae70346 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + + + + + @@ -65,6 +72,18 @@ + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/LogRecorder.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/LogRecorder.java new file mode 100644 index 0000000..ad4d54a --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/LogRecorder.java @@ -0,0 +1,540 @@ +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 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 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... + *

+ * Request alternate ring buffer, 'main', 'system', 'radio' + * or 'events'. Multiple -b parameters are allowed and the + * results are interleaved. + *

+ * 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. + *

+ * 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. + *

+ * 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; + } + } + +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt index 660e0d9..0b0eabe 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt @@ -32,9 +32,3 @@ class AngApplication : Application() { AngConfigManager.inject(this) } } - -val Context.v2RayApplication: AngApplication - get() = applicationContext as AngApplication - -val Context.defaultDPreference: DPreference - get() = v2RayApplication.defaultDPreference \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt index 7a347ca..fb0e309 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt @@ -24,4 +24,6 @@ object AppConfig { const val MSG_STATE_START_FAILURE = 32 const val MSG_STATE_STOP = 4 const val MSG_STATE_STOP_SUCCESS = 41 + const val MSG_STATE_RESTART = 5 + const val MSG_STATE_RESTART_SOFT = 6 } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt index f19c0e2..a38f15c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt @@ -15,5 +15,6 @@ data class AngConfig( var remarks: String = "def", var headerType: String = "", var requestHost: String = "", - var streamSecurity: String = "") + var streamSecurity: String = "", + var configType: Int = 1) } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AppInfo.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AppInfo.kt new file mode 100644 index 0000000..420be81 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AppInfo.kt @@ -0,0 +1,8 @@ +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) \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt new file mode 100644 index 0000000..4be0cbc --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt @@ -0,0 +1,20 @@ +package com.v2ray.ang.extension + +import android.content.Context +import com.v2ray.ang.AngApplication +import me.dozen.dpreference.DPreference +import org.json.JSONObject + +/** + * Some extensions + */ + +val Context.v2RayApplication: AngApplication + get() = applicationContext as AngApplication + +val Context.defaultDPreference: DPreference + get() = v2RayApplication.defaultDPreference + + +fun JSONObject.putOpt(pair: Pair) = putOpt(pair.first, pair.second)!! +fun JSONObject.putOpt(pairs: Map) = pairs.forEach { putOpt(it.key to it.value) } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt new file mode 100644 index 0000000..81878fd --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt @@ -0,0 +1,70 @@ +package com.v2ray.ang.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.widget.Toast +import com.v2ray.ang.AppConfig +import com.v2ray.ang.util.MessageUtil + + +class NetWorkStateReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + var isConnected: Boolean = false + + //检测API是不是小于23,因为到了API23之后getNetworkInfo(int networkType)方法被弃用 + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetwork = cm.activeNetworkInfo + if (activeNetwork != null) { // connected to the internet + if (activeNetwork.type == ConnectivityManager.TYPE_WIFI) { + // connected to wifi + isConnected = true + //Toast.makeText(context, activeNetwork.typeName, Toast.LENGTH_SHORT).show() + } else if (activeNetwork.type == ConnectivityManager.TYPE_MOBILE) { + // connected to the mobile provider's data plan + isConnected = true + //Toast.makeText(context, activeNetwork.typeName, Toast.LENGTH_SHORT).show() + } + } else { + // not connected to the internet + } + //API大于23时使用下面的方式进行网络监听 + } else { + //获得ConnectivityManager对象 + val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + //获取所有网络连接的信息 + val networks = connMgr.allNetworks + + //通过循环将网络信息逐个取出来 + loop@ for (i in networks.indices) { + //获取ConnectivityManager对象对应的NetworkInfo对象 + val networkInfo = connMgr.getNetworkInfo(networks[i]) + if (networkInfo.isConnected) { + if (networkInfo.type == ConnectivityManager.TYPE_WIFI) { + // connected to wifi + isConnected = true + //Toast.makeText(context, networkInfo.typeName, Toast.LENGTH_SHORT).show() + break@loop + } else if (networkInfo.type == ConnectivityManager.TYPE_MOBILE) { + // connected to mobile + isConnected = true + //Toast.makeText(context, networkInfo.typeName, Toast.LENGTH_SHORT).show() + break@loop + } + } + } + } + if (isConnected) { + sendMsg2Service(context) + } + } + + private fun sendMsg2Service(context: Context) { + Toast.makeText(context, "Restart v2ray", Toast.LENGTH_SHORT).show() + MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_RESTART_SOFT, "") + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt index 68e0122..aa99d60 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt @@ -11,6 +11,7 @@ import com.v2ray.ang.R import com.v2ray.ang.AppConfig import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.util.AngConfigManager +import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.Utils import org.jetbrains.anko.toast @@ -37,13 +38,8 @@ class WidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) { - if (Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")) { - val intent = Intent() - intent.action = AppConfig.BROADCAST_ACTION_SERVICE - intent.`package` = AppConfig.ANG_PACKAGE - intent.putExtra("key", AppConfig.MSG_STATE_STOP) - context.sendBroadcast(intent) + MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "") context.toast(R.string.toast_services_stop) } else { context.toast(R.string.toast_services_start) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt new file mode 100644 index 0000000..b714366 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt @@ -0,0 +1,98 @@ +package com.v2ray.ang.service + +import android.annotation.TargetApi +import android.content.BroadcastReceiver +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.AngConfigManager +import com.v2ray.ang.util.MessageUtil +import org.jetbrains.anko.toast +import java.lang.ref.SoftReference + + +@TargetApi(Build.VERSION_CODES.N) +class QSTileService : TileService() { + + fun setState(state: Int) { + 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_start_idle) + } 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_start_connected) + } + + + qsTile?.updateTile() + } + + override fun onStartListening() { + super.onStartListening() + setState(Tile.STATE_INACTIVE) + mMsgReceive = ReceiveMessageHandler(this) + registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)) + MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "") + } + + override fun onStopListening() { + super.onStopListening() + + unregisterReceiver(mMsgReceive) + mMsgReceive = null + } + + override fun onClick() { + super.onClick() + when (qsTile.state) { + Tile.STATE_INACTIVE -> { + val intent = VpnService.prepare(this) + if (intent == null) + if (AngConfigManager.genStoreV2rayConfig()) { + V2RayVpnService.startV2Ray(this) + } else { + toast(R.string.app_tile_first_use) + } + } + Tile.STATE_ACTIVE -> { + MessageUtil.sendMsg2Service(this, AppConfig.MSG_STATE_STOP, "") + } + } + } + + private var mMsgReceive: BroadcastReceiver? = null + + private class ReceiveMessageHandler(context: QSTileService) : BroadcastReceiver() { + internal var mReference: SoftReference = 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) + } + } + } + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 3da9d22..536ceec 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -6,27 +6,24 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager -import android.net.NetworkInfo +import android.net.ConnectivityManager import android.net.VpnService import android.os.* -import android.support.v7.app.NotificationCompat -import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork +import android.support.v4.app.NotificationCompat import com.orhanobut.logger.Logger import com.v2ray.ang.AppConfig import com.v2ray.ang.R -import com.v2ray.ang.defaultDPreference +import com.v2ray.ang.extension.defaultDPreference +import com.v2ray.ang.receiver.NetWorkStateReceiver import com.v2ray.ang.ui.MainActivity import com.v2ray.ang.ui.PerAppProxyActivity import com.v2ray.ang.ui.SettingsActivity +import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.Utils import libv2ray.Libv2ray import libv2ray.V2RayCallbacks import libv2ray.V2RayVPNServiceSupportsSet -import rx.Subscription -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers import java.lang.ref.SoftReference -import java.util.concurrent.TimeUnit class V2RayVpnService : VpnService() { companion object { @@ -42,10 +39,12 @@ class V2RayVpnService : VpnService() { private val v2rayPoint = Libv2ray.newV2RayPoint() private val v2rayCallback = V2RayCallback() - private var connectivitySubscription: Subscription? = null + // private var connectivitySubscription: Subscription? = null + private var netWorkStateReceiver: NetWorkStateReceiver? = null private lateinit var configContent: String private lateinit var mInterface: ParcelFileDescriptor val fd: Int get() = mInterface.fd + private var currentTimeMillis: Long = 0 override fun onCreate() { super.onCreate() @@ -125,9 +124,10 @@ class V2RayVpnService : VpnService() { v2rayPoint.vpnSupportReady() if (v2rayPoint.isRunning) { - sendMsg(AppConfig.MSG_STATE_START_SUCCESS, "") + MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "") + showNotification() } else { - sendMsg(AppConfig.MSG_STATE_START_FAILURE, "") + MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "") } } @@ -138,22 +138,21 @@ class V2RayVpnService : VpnService() { configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") - connectivitySubscription = ReactiveNetwork.observeNetworkConnectivity(this.applicationContext) - .subscribeOn(Schedulers.io()) - .skip(1) - //.filter(Connectivity.hasState(NetworkInfo.State.CONNECTED)) - .throttleWithTimeout(3, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { connectivity -> - val state = connectivity.state - Logger.e("ReactiveNetwork", state.toString()) - if (state == NetworkInfo.State.CONNECTED) { - if (v2rayPoint.isRunning) { - v2rayPoint.networkInterrupted() - } - } - } +// connectivitySubscription = ReactiveNetwork.observeNetworkConnectivity(this.applicationContext) +// .subscribeOn(Schedulers.io()) +// //.filter(Connectivity.hasState(NetworkInfo.State.CONNECTED)) +// //.throttleWithTimeout(3, TimeUnit.SECONDS) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe { connectivity -> +// val state = connectivity.state +// Logger.e(state.toString()) +// //if (state == NetworkInfo.State.CONNECTED) { +// if (v2rayPoint.isRunning) { +// v2rayPoint.networkInterrupted() +// } +// //} +// v2rayPoint.callbacks = v2rayCallback // v2rayPoint.vpnSupportSet = v2rayCallback v2rayPoint.setVpnSupportSet(v2rayCallback) @@ -164,7 +163,7 @@ class V2RayVpnService : VpnService() { v2rayPoint.runLoop() } - showNotification() + // showNotification() } private fun stopV2Ray() { @@ -178,17 +177,28 @@ class V2RayVpnService : VpnService() { } unregisterReceiver(mMsgReceive) + unregisterReceiver(netWorkStateReceiver) - connectivitySubscription?.let { - it.unsubscribe() - connectivitySubscription = null - } +// connectivitySubscription?.let { +// it.unsubscribe() +// connectivitySubscription = null +// } - sendMsg(AppConfig.MSG_STATE_STOP_SUCCESS, "") + + MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "") cancelNotification() stopSelf() } + private fun restartV2RaySoft() { + if (System.currentTimeMillis() > currentTimeMillis + 2000) { + if (v2rayPoint.isRunning) { + v2rayPoint.networkInterrupted() + } + currentTimeMillis = System.currentTimeMillis() + } + } + private fun showNotification() { val startMainIntent = Intent(applicationContext, MainActivity::class.java) val contentPendingIntent = PendingIntent.getActivity(applicationContext, @@ -203,7 +213,7 @@ class V2RayVpnService : VpnService() { NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val notification = NotificationCompat.Builder(applicationContext) + val notification = NotificationCompat.Builder(applicationContext, "M_CH_ID") .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) .setContentText(getString(R.string.notification_action_more)) @@ -215,6 +225,11 @@ class V2RayVpnService : VpnService() { .build() startForeground(NOTIFICATION_ID, notification) + + if (netWorkStateReceiver == null) { + netWorkStateReceiver = NetWorkStateReceiver() + } + registerReceiver(netWorkStateReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) } private fun cancelNotification() { @@ -264,9 +279,9 @@ class V2RayVpnService : VpnService() { val isRunning = vpnService?.v2rayPoint!!.isRunning && VpnService.prepare(vpnService) == null if (isRunning) { - vpnService?.sendMsg(AppConfig.MSG_STATE_RUNNING, "") + MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_RUNNING, "") } else { - vpnService?.sendMsg(AppConfig.MSG_STATE_NOT_RUNNING, "") + MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_NOT_RUNNING, "") } } AppConfig.MSG_UNREGISTER_CLIENT -> { @@ -278,21 +293,11 @@ class V2RayVpnService : VpnService() { AppConfig.MSG_STATE_STOP -> { vpnService?.stopV2Ray() } + AppConfig.MSG_STATE_RESTART_SOFT -> { + vpnService?.restartV2RaySoft() + } } } } - - fun sendMsg(what: Int, content: String) { - try { - val intent = Intent() - intent.action = AppConfig.BROADCAST_ACTION_ACTIVITY - intent.`package` = AppConfig.ANG_PACKAGE - intent.putExtra("key", what) - sendBroadcast(intent) - } catch (e: Exception) { - e.printStackTrace() - } - } - } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 6d3bb12..1555c79 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -2,8 +2,9 @@ package com.v2ray.ang.ui import android.Manifest import android.content.* +import android.net.ConnectivityManager +import android.net.Uri import android.net.VpnService -import android.os.* import android.support.v7.widget.LinearLayoutManager import android.view.Menu import android.view.MenuItem @@ -13,21 +14,23 @@ import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.Utils import kotlinx.android.synthetic.main.activity_main.* -import org.jetbrains.anko.imageResource -import org.jetbrains.anko.startActivity -import org.jetbrains.anko.toast import android.os.Bundle +import android.text.TextUtils import android.view.KeyEvent import com.v2ray.ang.AppConfig -import org.jetbrains.anko.startActivityForResult +import com.v2ray.ang.util.MessageUtil +import com.v2ray.ang.util.V2rayConfigUtil +import org.jetbrains.anko.* import java.lang.ref.SoftReference -import android.view.KeyEvent.KEYCODE_BACK - +import java.net.URL +import android.content.IntentFilter class MainActivity : BaseActivity() { 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 } var fabChecked = false @@ -35,9 +38,9 @@ class MainActivity : BaseActivity() { field = value adapter.changeable = !value if (value) { - fab.imageResource = R.drawable.ic_fab_check + fab.imageResource = R.drawable.ic_start_connected } else { - fab.imageResource = R.drawable.ic_fab_uncheck + fab.imageResource = R.drawable.ic_start_idle } } @@ -49,13 +52,14 @@ class MainActivity : BaseActivity() { fab.setOnClickListener { if (fabChecked) { - sendMsg(AppConfig.MSG_STATE_STOP, "") + MessageUtil.sendMsg2Service(this, AppConfig.MSG_STATE_STOP, "") } else { val intent = VpnService.prepare(this) - if (intent == null) + if (intent == null) { startV2Ray() - else + } else { startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE) + } } } @@ -80,7 +84,7 @@ class MainActivity : BaseActivity() { mMsgReceive = ReceiveMessageHandler(this@MainActivity) registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY)) - sendMsg(AppConfig.MSG_REGISTER_CLIENT, "") + MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "") } override fun onStop() { @@ -103,16 +107,23 @@ class MainActivity : BaseActivity() { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { REQUEST_CODE_VPN_PREPARE -> - startV2Ray() + if (resultCode == RESULT_OK) { + startV2Ray() + } REQUEST_SCAN -> - importConfig(data?.getStringExtra("SCAN_RESULT")) -// IntentIntegrator.REQUEST_CODE -> { -// if (resultCode == RESULT_CANCELED) { -// } else { -// val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) -// importConfig(scanResult.contents) -// } -// } + if (resultCode == RESULT_OK) { + importConfig(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")) + } } } @@ -123,7 +134,7 @@ class MainActivity : BaseActivity() { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.import_qrcode -> { - importQRcode() + importQRcode(REQUEST_SCAN) true } R.id.import_clipboard -> { @@ -135,6 +146,18 @@ class MainActivity : BaseActivity() { adapter.updateConfigList() 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) + true + } R.id.settings -> { startActivity("isRunning" to fabChecked) true @@ -150,23 +173,21 @@ class MainActivity : BaseActivity() { /** * import config from qrcode */ - fun importQRcode(): Boolean { + fun importQRcode(requestCode: Int): Boolean { try { startActivityForResult(Intent("com.google.zxing.client.android.SCAN") .addCategory(Intent.CATEGORY_DEFAULT) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), REQUEST_SCAN) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) } catch (e: Exception) { RxPermissions.getInstance(this) .request(Manifest.permission.CAMERA) .subscribe { if (it) - startActivityForResult(REQUEST_SCAN) + startActivityForResult(requestCode) else toast(R.string.toast_permission_denied) } } -// val integrator = IntentIntegrator(this) -// integrator.initiateScan(IntentIntegrator.ALL_CODE_TYPES) return true } @@ -197,6 +218,112 @@ class MainActivity : BaseActivity() { } } + /** + * import config from local config file + */ + fun importConfigCustomLocal(): Boolean { + try { + showFileChooser() + } catch (e: Exception) { + e.printStackTrace() + return false + } + return true + } + + fun importConfigCustomUrlClipboard(): Boolean { + try { + val url = Utils.getClipboard(this) + if (TextUtils.isEmpty(url)) { + toast(R.string.toast_none_data_clipboard) + return false + } + return importConfigCustomUrl(url) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + + /** + * import config from url + */ + 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 { + importCustomizeConfig(configText) + } + } + } catch (e: Exception) { + e.printStackTrace() + return false + } + return true + } + + /** + * show file chooser + */ + private fun showFileChooser() { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "*/*" + intent.addCategory(Intent.CATEGORY_OPENABLE) + + try { + startActivityForResult( + Intent.createChooser(intent, getString(R.string.title_file_chooser)), + REQUEST_FILE_CHOOSER) + } catch (ex: android.content.ActivityNotFoundException) { + toast(R.string.toast_require_file_manager) + } + } + + /** + * read content from uri + */ + private fun readContentFromUri(uri: Uri) { + RxPermissions.getInstance(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() + } + } else + toast(R.string.toast_permission_denied) + } + } + + /** + * import customize config + */ + fun importCustomizeConfig(server: String?) { + if (server == null) { + return + } + if (!V2rayConfigUtil.isValidConfig(server)) { + toast(R.string.toast_config_file_invalid) + return + } + val resId = AngConfigManager.importCustomizeConfig(server) + if (resId > 0) { + toast(resId) + } else { + toast(R.string.toast_success) + adapter.updateConfigList() + } + } + // val mConnection = object : ServiceConnection { // override fun onServiceDisconnected(name: ComponentName?) { // } @@ -234,18 +361,6 @@ class MainActivity : BaseActivity() { } } - fun sendMsg(what: Int, content: String) { - try { - val intent = Intent() - intent.action = AppConfig.BROADCAST_ACTION_SERVICE - intent.`package` = AppConfig.ANG_PACKAGE - intent.putExtra("key", what) - sendBroadcast(intent) - } catch (e: Exception) { - e.printStackTrace() - } - } - override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { moveTaskToBack(false) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 2cff0a1..2373cbf 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -41,16 +41,24 @@ class MainRecyclerAdapter(val activity: BaseActivity) : RecyclerView.Adapter @@ -80,7 +88,11 @@ class MainRecyclerAdapter(val activity: BaseActivity) : RecyclerView.Adapter("position" to position, "isRunning" to !changeable) + if (configType == 1) { + mActivity.startActivity("position" to position, "isRunning" to !changeable) + } else if (configType == 2) { + mActivity.startActivity("position" to position, "isRunning" to !changeable) + } } holder.infoContainer.setOnClickListener { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyActivity.kt index 27603c2..10c8916 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyActivity.kt @@ -3,8 +3,10 @@ package com.v2ray.ang.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.app.ProgressDialog +import android.content.Context import android.os.Bundle import android.support.v7.widget.RecyclerView +import android.text.TextUtils import android.view.Menu import android.view.MenuItem import android.view.View @@ -12,14 +14,16 @@ import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import com.dinuscxj.itemdecoration.LinearDividerItemDecoration import com.v2ray.ang.R -import com.v2ray.ang.defaultDPreference -import com.v2ray.ang.util.AppInfo +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 com.v2ray.ang.dto.AppInfo class PerAppProxyActivity : BaseActivity() { companion object { @@ -28,6 +32,7 @@ class PerAppProxyActivity : BaseActivity() { } private var adapter: PerAppProxyAdapter? = null + private var appsAll: List? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -56,6 +61,7 @@ class PerAppProxyActivity : BaseActivity() { } .observeOn(AndroidSchedulers.mainThread()) .subscribe { + appsAll = it val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null) adapter = PerAppProxyAdapter(this, it, blacklist) recycler_view.adapter = adapter @@ -64,7 +70,7 @@ class PerAppProxyActivity : BaseActivity() { recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() { var dst = 0 - val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 2 + 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) { @@ -130,6 +136,34 @@ class PerAppProxyActivity : BaseActivity() { container_bypass_apps.setOnClickListener { switch_bypass_apps.performClick() } + + 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) + + val key = v.text.toString().toUpperCase() + val apps = ArrayList() + 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() { @@ -147,15 +181,22 @@ class PerAppProxyActivity : BaseActivity() { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.select_all -> adapter?.let { val pkgNames = it.apps.map { it.packageName } - if (it.blacklist.containsAll(pkgNames)) - it.blacklist.clear() - else - it.blacklist.addAll(pkgNames) - + if (it.blacklist.containsAll(pkgNames)) { + it.apps.forEach { + val packageName = it.packageName + adapter?.blacklist!!.remove(packageName) + } + } else { + it.apps.forEach { + val packageName = it.packageName + adapter?.blacklist!!.add(packageName) + } + } it.notifyDataSetChanged() true } ?: false else -> super.onOptionsItemSelected(item) } + } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyAdapter.kt index 98fc66e..95c3ea9 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/PerAppProxyAdapter.kt @@ -1,13 +1,15 @@ package com.v2ray.ang.ui +import android.graphics.Color import android.support.v7.widget.RecyclerView import android.view.View import android.view.ViewGroup import com.v2ray.ang.R -import com.v2ray.ang.util.AppInfo +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, blacklist: MutableSet?) : @@ -37,7 +39,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl VIEW_TYPE_HEADER -> { val view = View(ctx) view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 2) + ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 3) BaseViewHolder(view) } @@ -75,10 +77,10 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl if (appInfo.isSystemApp) { name.text = String.format("** %1s", appInfo.appName) - //name.textColor = ContextCompat.getColor(mActivity, R.color.material_blue_grey_900) + name.textColor = Color.RED } else { name.text = appInfo.appName - //name.textColor = ContextCompat.getColor(mActivity, R.color.abc_secondary_text_material_light) + name.textColor = Color.DKGRAY } itemView.setOnClickListener(this) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt new file mode 100644 index 0000000..b90a318 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt @@ -0,0 +1,140 @@ +package com.v2ray.ang.ui + +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import com.v2ray.ang.AppConfig +import com.v2ray.ang.R +import com.v2ray.ang.extension.defaultDPreference +import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.util.AngConfigManager +import com.v2ray.ang.util.Utils +import com.v2ray.ang.extension.alert +import kotlinx.android.synthetic.main.activity_server2.* +import org.jetbrains.anko.* + + +class Server2Activity : BaseActivity() { + companion object { + private const val REQUEST_SCAN = 1 + } + + var del_config: MenuItem? = null + var save_config: MenuItem? = null + + private lateinit var configs: AngConfig + private var edit_index: Int = -1 //当前编辑的服务器 + private var edit_guid: String = "" + private var isRunning: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_server2) + + configs = AngConfigManager.configs + edit_index = intent.getIntExtra("position", -1) + isRunning = intent.getBooleanExtra("isRunning", false) + title = getString(R.string.title_server) + + if (edit_index >= 0) { + edit_guid = configs.vmess[edit_index].guid + bindingServer(configs.vmess[edit_index]) + } else { + clearServer() + } + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + /** + * bingding seleced server config + */ + fun bindingServer(vmess: AngConfig.VmessBean): Boolean { + et_remarks.text = Utils.getEditable(vmess.remarks) + tv_content.text = defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + edit_guid, "") + return true + } + + /** + * clear or init server config + */ + fun clearServer(): Boolean { + et_remarks.text = null + return true + } + + /** + * save server config + */ + fun saveServer(): Boolean { + val vmess = configs.vmess[edit_index] + + vmess.remarks = et_remarks.text.toString() + + if (TextUtils.isEmpty(vmess.remarks)) { + toast(R.string.server_lab_remarks) + return false + } + + if (AngConfigManager.addServer(vmess, edit_index) == 0) { + toast(R.string.toast_success) + finish() + return true + } else { + toast(R.string.toast_failure) + return false + } + } + + /** + * save server config + */ + fun deleteServer(): Boolean { + if (edit_index >= 0) { + alert(R.string.del_config_comfirm) { + positiveButton(android.R.string.ok) { + if (AngConfigManager.removeServer(edit_index) == 0) { + toast(R.string.toast_success) + finish() + } else { + toast(R.string.toast_failure) + } + } + show() + } + } else { + } + return true + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.action_server, menu) + del_config = menu?.findItem(R.id.del_config) + save_config = menu?.findItem(R.id.save_config) + + if (edit_index >= 0) { + if (isRunning) { + if (edit_index == configs.index) { + del_config?.isVisible = false + save_config?.isVisible = false + } + } + } else { + del_config?.isVisible = false + } + + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.del_config -> { + deleteServer() + true + } + R.id.save_config -> { + saveServer() + true + } + else -> super.onOptionsItemSelected(item) + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 373d25a..11d4a57 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -12,9 +12,10 @@ import android.support.v7.app.AppCompatActivity import com.v2ray.ang.BuildConfig import com.v2ray.ang.InappBuyActivity import com.v2ray.ang.R -import com.v2ray.ang.defaultDPreference +import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.onClick import de.psdev.licensesdialog.LicensesDialogFragment +import libv2ray.Libv2ray import org.jetbrains.anko.act import org.jetbrains.anko.defaultSharedPreferences import org.jetbrains.anko.startActivity @@ -67,7 +68,7 @@ class SettingsActivity : BaseActivity() { } feedback.onClick { - openUri("https://github.com/v2ray/v2rayNG/issues") + openUri("https://github.com/2dust/v2rayNG/issues") } perAppProxy.setOnPreferenceClickListener { @@ -81,7 +82,7 @@ class SettingsActivity : BaseActivity() { true } - version.summary = BuildConfig.VERSION_NAME + version.summary = "${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})" } override fun onStart() { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index f1705e0..a2809e4 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -283,4 +283,45 @@ object AngConfigManager { } } + /** + * import customize config + */ + fun importCustomizeConfig(server: String?): Int { + try { + if (server == null || TextUtils.isEmpty(server)) { + return R.string.toast_none_data + } + + val guid = System.currentTimeMillis().toString() + app.defaultDPreference.setPrefString(ANG_CONFIG + guid, server) + + //add + val vmess = AngConfig.VmessBean() + vmess.configType = 2 + vmess.guid = guid + vmess.remarks = vmess.guid + + vmess.security = "" + vmess.network = "" + vmess.headerType = "" + vmess.address = "" + vmess.port = 0 + vmess.id = "" + vmess.alterId = 0 + vmess.network = "" + vmess.headerType = "" + vmess.requestHost = "" + vmess.streamSecurity = "" + + angConfig.vmess.add(vmess) + if (angConfig.vmess.count() == 1) { + angConfig.index = 0 + } + storeConfigFile() + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AppManagerUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AppManagerUtil.kt index b215a40..f5b57f3 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AppManagerUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AppManagerUtil.kt @@ -5,7 +5,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager -import android.graphics.drawable.Drawable +import com.v2ray.ang.dto.AppInfo import rx.Observable import java.util.* @@ -39,6 +39,4 @@ object AppManagerUtil { val permissions = requestedPermissions return permissions?.any { it == Manifest.permission.INTERNET } ?: false } -} - -data class AppInfo(val appName: String, val packageName: String, val appIcon: Drawable, val isSystemApp: Boolean) \ No newline at end of file +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MessageUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MessageUtil.kt new file mode 100644 index 0000000..486023c --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MessageUtil.kt @@ -0,0 +1,30 @@ +package com.v2ray.ang.util + +import android.content.Context +import android.content.Intent +import com.v2ray.ang.AppConfig + + +object MessageUtil { + + fun sendMsg2Service(ctx: Context, what: Int, content: String) { + sendMsg(ctx, AppConfig.BROADCAST_ACTION_SERVICE, what, content) + } + + fun sendMsg2UI(ctx: Context, what: Int, content: String) { + sendMsg(ctx, AppConfig.BROADCAST_ACTION_ACTIVITY, what, content) + } + + private fun sendMsg(ctx: Context, action: String, what: Int, content: String) { + try { + val intent = Intent() + intent.action = action + intent.`package` = AppConfig.ANG_PACKAGE + intent.putExtra("key", what) + intent.putExtra("content", content) + ctx.sendBroadcast(intent) + } catch (e: Exception) { + e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 6327d90..ab11e6c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -12,6 +12,8 @@ import com.google.zxing.EncodeHintType import java.util.* import kotlin.collections.HashMap import android.app.ActivityManager +import android.util.Patterns +import android.webkit.URLUtil import com.orhanobut.logger.Logger import java.util.logging.LogManager @@ -176,6 +178,22 @@ object Utils { } } + /** + * is valid url + */ + fun isValidUrl(value: String?): Boolean { + try { + if (Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) { + return true + } + } catch (e: WriterException) { + e.printStackTrace() + return false + } + return false + } + + /** * 判断服务是否后台运行 diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index e531805..3d458e3 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -3,16 +3,18 @@ package com.v2ray.ang.util import android.text.TextUtils import com.google.gson.Gson import com.v2ray.ang.AngApplication +import com.v2ray.ang.AppConfig import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.V2rayConfig +import com.v2ray.ang.extension.putOpt import com.v2ray.ang.ui.SettingsActivity -import org.json.JSONObject import org.json.JSONArray -import java.util.LinkedHashSet - +import org.json.JSONException +import org.json.JSONObject +import java.util.* object V2rayConfigUtil { - val lib2rayObj: JSONObject by lazy { + private val lib2rayObj: JSONObject by lazy { JSONObject("""{ "enabled": true, "listener": { @@ -55,19 +57,64 @@ object V2rayConfigUtil { }""") } - val requestObj: JSONObject by lazy { + private val requestObj: JSONObject by lazy { JSONObject("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") } - val responseObj: JSONObject by lazy { + private val responseObj: JSONObject by lazy { JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") } + private val replacementPairs by lazy { + mapOf("port" to 10808, + "inbound" to JSONObject("""{ + "protocol": "socks", + "listen": "127.0.0.1", + "settings": { + "auth": "noauth", + "udp": true + }, + "domainOverride": ["http", "tls"] + }"""), + "inboundDetour" to JSONArray(), + "#lib2ray" to lib2rayObj, + "log" to JSONObject("""{ + "loglevel": "warning" + }""") + ) + } + data class Result(var status: Boolean, var content: String) /** * 生成v2ray的客户端配置文件 */ fun getV2rayConfig(app: AngApplication, config: AngConfig): Result { + var result = Result(false, "") + try { + //检查设置 + if (config.index < 0 + || config.vmess.count() <= 0 + || config.index > config.vmess.count() - 1 + ) { + return result + } + + if (config.vmess[config.index].configType == 1) { + result = getV2rayConfigType1(app, config) + } else if (config.vmess[config.index].configType == 2) { + result = getV2rayConfigType2(app, config) + } + return result + } catch (e: Exception) { + e.printStackTrace() + return result + } + } + + /** + * 生成v2ray的客户端配置文件 + */ + private fun getV2rayConfigType1(app: AngApplication, config: AngConfig): Result { val result = Result(false, "") try { //检查设置 @@ -112,6 +159,36 @@ object V2rayConfigUtil { } } + /** + * 生成v2ray的客户端配置文件 + */ + private fun getV2rayConfigType2(app: AngApplication, config: AngConfig): Result { + val result = Result(false, "") + try { + //检查设置 + if (config.index < 0 + || config.vmess.count() <= 0 + || config.index > config.vmess.count() - 1 + ) { + return result + } + val vmess = config.vmess[config.index] + val guid = vmess.guid; + val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "") + + //增加lib2ray + val finalConfig = addLib2ray2(jsonConfig) + + result.status = true + result.content = finalConfig + return result + + } catch (e: Exception) { + e.printStackTrace() + return result + } + } + /** * vmess协议服务器配置 */ @@ -217,7 +294,7 @@ object V2rayConfigUtil { /** * routing */ - fun routing(config: AngConfig, v2rayConfig: V2rayConfig): Boolean { + private fun routing(config: AngConfig, v2rayConfig: V2rayConfig): Boolean { try { //绕过大陆网址 if (config.bypassMainland) { @@ -241,7 +318,7 @@ object V2rayConfigUtil { /** * Custom Dns */ - fun customDns(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { + private fun customDns(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { v2rayConfig.dns.servers = getRemoteDnsServers(app) } catch (e: Exception) { @@ -270,7 +347,7 @@ object V2rayConfigUtil { /** * get remote dns servers from preference */ - fun getRemoteDnsServers(app: AngApplication): List { + private fun getRemoteDnsServers(app: AngApplication): List { val ret = ArrayList() val remoteDns = app.defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, "") if (!TextUtils.isEmpty(remoteDns)) { @@ -294,4 +371,51 @@ object V2rayConfigUtil { } return ret } -} + + /** + * 增加lib2ray + */ + private fun addLib2ray2(jsonConfig: String): String { + try { + val jObj = JSONObject(jsonConfig) + //find outbound address and port + try { + if (jObj.has("outbound") + || jObj.optJSONObject("outbound").has("settings") + || jObj.optJSONObject("outbound").optJSONObject("settings").has("vnext")) { + val vnext = jObj.optJSONObject("outbound").optJSONObject("settings").optJSONArray("vnext") + for (i in 0..(vnext.length() - 1)) { + val item = vnext.getJSONObject(i) + val address = item.getString("address") + val port = item.getString("port") + if (!Utils.isIpAddress(address)) { + lib2rayObj.optJSONObject("preparedDomainName") + .optJSONArray("domainName") + .put(String.format("%s:%s", address, port)) + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + + jObj.putOpt(replacementPairs) + return jObj.toString() + } catch (e: Exception) { + e.printStackTrace() + return "" + } + } + + /** + * is valid config + */ + fun isValidConfig(conf: String): Boolean { + try { + val jObj = JSONObject(conf) + return jObj.has("outbound") and jObj.has("inbound") + } catch (e: JSONException) { + return false + } + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_busy.xml b/V2rayNG/app/src/main/res/drawable/ic_start_busy.xml new file mode 100644 index 0000000..f0154c3 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_start_busy.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml b/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml new file mode 100644 index 0000000..bc8c366 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml b/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml new file mode 100644 index 0000000..ef9b99d --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml b/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml index 8ce0e3a..636fb58 100644 --- a/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml +++ b/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml @@ -21,10 +21,8 @@ android:layout_height="wrap_content" android:background="#f8f8f8" android:orientation="vertical" - android:paddingEnd="0dp" - android:paddingLeft="46dp" - android:paddingRight="0dp" - android:paddingStart="46dp"> + android:paddingEnd="20dp" + android:paddingStart="20dp"> + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/activity_server2.xml b/V2rayNG/app/src/main/res/layout/activity_server2.xml new file mode 100644 index 0000000..5361d3f --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_server2.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_main.xml b/V2rayNG/app/src/main/res/menu/menu_main.xml index ffd1172..4667775 100644 --- a/V2rayNG/app/src/main/res/menu/menu_main.xml +++ b/V2rayNG/app/src/main/res/menu/menu_main.xml @@ -18,6 +18,19 @@ android:id="@+id/import_manually" android:title="@string/menu_item_import_config_manually" app:showAsAction="never" /> + + + + v2rayNG 开关 + 开关 + 初次使用此功能请先用APP激活VPN 停止 @@ -20,6 +22,9 @@ 扫描二维码 从剪贴板导入 手动输入 + 从本地导入自定义配置 + 从URL导入自定义配置 + 扫描URL导入自定义配置 确认删除? 别名(remarks) 地址(address) @@ -37,12 +42,20 @@ 没有数据 不正确的协议 解码失败 + 选择一个配置文件 + 请安装一个文件管理器 + 自定义配置 + 无效的配置文件 + 内容 + 剪贴板中没有数据 + 无效的网址 正在加载 全选 选中的应用不走代理 选中的应用走代理 + 输入关键字 设置 @@ -56,7 +69,7 @@ 分应用代理仅支持 Android 5.0 Lollipop 及更高 捐赠 - 支持开发者,并激活进阶功能 + 支持开发者 陆续增加一些试验性的进阶功能 远程DNS @@ -72,6 +85,7 @@ 错误: 错误,真实性验证失败. 感谢您的捐赠! + 捐赠 二维码 diff --git a/V2rayNG/app/src/main/res/values/dimens.xml b/V2rayNG/app/src/main/res/values/dimens.xml index f7f22ce..62e38ab 100644 --- a/V2rayNG/app/src/main/res/values/dimens.xml +++ b/V2rayNG/app/src/main/res/values/dimens.xml @@ -1,6 +1,6 @@ - 46dp + 50dp 16dp 16dp 50dp diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index a090b85..a5ae358 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -2,6 +2,8 @@ v2rayNG Switch + Switch + First use of this feature, please use the app to activate VPN Stop @@ -20,6 +22,9 @@ Import config from QRcode Import config from Clipboard Type manually + Import custom config from locally + Import custom config from URL + Import custom config scan URL Confirm delete? remarks address @@ -37,14 +42,20 @@ There is nothing Incorrect protocol Decoding failed - - - Don\'t proxy selected apps - Proxy selected apps + Select a Config File + Please install a File Manager. + Customize Config + Invalid Config + Content + There is no data in the clipboard + Invalid URL Loading Select all + Don\'t proxy selected apps + Proxy selected apps + Enter keywords @@ -59,7 +70,7 @@ Per-app proxy mode only support Android 5.0 Lollipop or higher Donate - Donate developer, and activate advanced features + Donate developer Add some experimental advanced features Remote DNS @@ -75,6 +86,7 @@ Error Purchase: Error Purchase,Authenticity verification failed. Thank you for your donation! + Donate QRcode diff --git a/V2rayNG/build.gradle b/V2rayNG/build.gradle index 1029b1b..b1ca039 100644 --- a/V2rayNG/build.gradle +++ b/V2rayNG/build.gradle @@ -3,9 +3,11 @@ buildscript { repositories { jcenter() + google() + } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath 'com.google.gms:google-services:3.0.0' @@ -17,6 +19,7 @@ buildscript { allprojects { repositories { jcenter() + google() } } diff --git a/V2rayNG/gradle.properties b/V2rayNG/gradle.properties index 39146d6..b146b67 100644 --- a/V2rayNG/gradle.properties +++ b/V2rayNG/gradle.properties @@ -13,11 +13,11 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #Fri Jun 02 14:08:42 CST 2017 -ankoVersion=0.10.1 -kotlinVersion=1.1.3-2 -supportLibVersion=25.3.1 -buildToolsVer=25.0.2 -compileSdkVer=25 +ankoVersion=0.10.2 +kotlinVersion=1.1.60 +supportLibVersion=27.0.1 +buildToolsVer=27.0.1 +compileSdkVer=27 kotlin.incremental=true -targetSdkVer=25 -firebaseVersion=10.2.1 +targetSdkVer=27 +firebaseVersion=11.6.0 From 6f7518b71b110c3e8ced9bcd1b252cb0191737b4 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Thu, 23 Nov 2017 08:44:36 +0800 Subject: [PATCH 11/49] Update README.md --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bf5b1ef..6eb6279 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ -# v2rayNG \ No newline at end of file +# v2rayNG + + +Get it on Google Play + + +Project V core +https://github.com/v2ray/v2ray-core/releases + +User manual +https://www.v2ray.com/ + +### Donate +If you like v2rayNG and want to support the developer +BTC: [1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3](https://blockchain.info/address/1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3 "1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3") From 70a3b8853fc24a66c502486f804941ada0f5cf1f Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Fri, 24 Nov 2017 08:10:25 +0800 Subject: [PATCH 12/49] Create LICENSE --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 131bed4b8bee9b39ab42ed791a11c8607c6d292f Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Mon, 27 Nov 2017 10:53:41 +0800 Subject: [PATCH 13/49] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6eb6279..81dec00 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,5 @@ https://www.v2ray.com/ ### Donate If you like v2rayNG and want to support the developer -BTC: [1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3](https://blockchain.info/address/1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3 "1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3") +BTC: [1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3](https://blockchain.info/address/1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3 "1K69UnL44D5psuSgwrzFQCdjnRbvd5taE3") +Paypal: [paypal.me/CaptainIronNG](https://www.paypal.me/CaptainIronNG/18 "paypal.me/CaptainIronNG") From ef9445a135894f6f0fe73da2acd58a0876b4c80c Mon Sep 17 00:00:00 2001 From: 2dust Date: Tue, 28 Nov 2017 13:24:05 +0800 Subject: [PATCH 14/49] bug fix --- .gitignore | 7 +- V2rayNG/app/build.gradle | 4 +- V2rayNG/app/src/main/AndroidManifest.xml | 2 +- V2rayNG/app/src/main/assets/v2ray_config.json | 4 +- .../kotlin/com/v2ray/ang/dto/AngConfig.kt | 1 + .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 4 +- .../ang/receiver/NetWorkStateReceiver.kt | 82 ++++++++++--------- .../com/v2ray/ang/service/V2RayVpnService.kt | 16 ++-- .../com/v2ray/ang/ui/SettingsActivity.kt | 1 + .../com/v2ray/ang/util/AngConfigManager.kt | 2 + .../com/v2ray/ang/util/V2rayConfigUtil.kt | 25 +++--- .../app/src/main/res/values-zh/strings.xml | 3 + V2rayNG/app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/xml/pref_settings.xml | 5 ++ 14 files changed, 93 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 0202f9c..bd40895 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -V2rayNG/app/src/main/res/layout/activity_inapp_buy.xml \ No newline at end of file +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 \ No newline at end of file diff --git a/V2rayNG/app/build.gradle b/V2rayNG/app/build.gradle index 79b9102..18a4775 100644 --- a/V2rayNG/app/build.gradle +++ b/V2rayNG/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.v2ray.ang" minSdkVersion 17 targetSdkVersion Integer.parseInt("$targetSdkVer") - versionCode 52 - versionName "0.2.4" + versionCode 55 + versionName "0.2.6" } signingConfigs { diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index ae70346..f081cd9 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -82,7 +82,7 @@ - + diff --git a/V2rayNG/app/src/main/assets/v2ray_config.json b/V2rayNG/app/src/main/assets/v2ray_config.json index c6ea198..98c76c5 100644 --- a/V2rayNG/app/src/main/assets/v2ray_config.json +++ b/V2rayNG/app/src/main/assets/v2ray_config.json @@ -34,7 +34,7 @@ "network": "tcp" }, "mux": { - "enabled": true + "enabled": false } }, "outboundDetour": [ @@ -58,6 +58,8 @@ "rules": [ { "type": "field", + "domain": [ + ], "ip": [ "0.0.0.0/8", "10.0.0.0/8", diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt index a38f15c..0f728ed 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt @@ -3,6 +3,7 @@ package com.v2ray.ang.dto data class AngConfig( var index: Int, var bypassMainland: Boolean = false, + var muxEnabled: Boolean = false, var vmess: ArrayList ) { data class VmessBean(var guid: String = "123456", diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt index 170feee..7150b7f 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt @@ -93,8 +93,8 @@ data class V2rayConfig(val port: Int, data class RulesBean(var type: String, var port: String, - var ip: List?, - var domain: List?, + var ip: ArrayList?, + var domain: ArrayList?, var outboundTag: String) } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt index 81878fd..78e0efc 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/NetWorkStateReceiver.kt @@ -11,52 +11,56 @@ import com.v2ray.ang.util.MessageUtil class NetWorkStateReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - var isConnected: Boolean = false + var isConnected = false - //检测API是不是小于23,因为到了API23之后getNetworkInfo(int networkType)方法被弃用 - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + try { + //检测API是不是小于23,因为到了API23之后getNetworkInfo(int networkType)方法被弃用 + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val activeNetwork = cm.activeNetworkInfo - if (activeNetwork != null) { // connected to the internet - if (activeNetwork.type == ConnectivityManager.TYPE_WIFI) { - // connected to wifi - isConnected = true - //Toast.makeText(context, activeNetwork.typeName, Toast.LENGTH_SHORT).show() - } else if (activeNetwork.type == ConnectivityManager.TYPE_MOBILE) { - // connected to the mobile provider's data plan - isConnected = true - //Toast.makeText(context, activeNetwork.typeName, Toast.LENGTH_SHORT).show() - } - } else { - // not connected to the internet - } - //API大于23时使用下面的方式进行网络监听 - } else { - //获得ConnectivityManager对象 - val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - - //获取所有网络连接的信息 - val networks = connMgr.allNetworks - - //通过循环将网络信息逐个取出来 - loop@ for (i in networks.indices) { - //获取ConnectivityManager对象对应的NetworkInfo对象 - val networkInfo = connMgr.getNetworkInfo(networks[i]) - if (networkInfo.isConnected) { - if (networkInfo.type == ConnectivityManager.TYPE_WIFI) { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetwork = cm.activeNetworkInfo + if (activeNetwork != null) { // connected to the internet + if (activeNetwork.type == ConnectivityManager.TYPE_WIFI) { // connected to wifi isConnected = true - //Toast.makeText(context, networkInfo.typeName, Toast.LENGTH_SHORT).show() - break@loop - } else if (networkInfo.type == ConnectivityManager.TYPE_MOBILE) { - // connected to mobile + //Toast.makeText(context, activeNetwork.typeName, Toast.LENGTH_SHORT).show() + } else if (activeNetwork.type == ConnectivityManager.TYPE_MOBILE) { + // connected to the mobile provider's data plan isConnected = true - //Toast.makeText(context, networkInfo.typeName, Toast.LENGTH_SHORT).show() - break@loop + //Toast.makeText(context, activeNetwork.typeName, Toast.LENGTH_SHORT).show() + } + } else { + // not connected to the internet + } + //API大于23时使用下面的方式进行网络监听 + } else { + //获得ConnectivityManager对象 + val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + //获取所有网络连接的信息 + val networks = connMgr.allNetworks + if (networks != null) { + //通过循环将网络信息逐个取出来 + loop@ for (i in networks.indices) { + //获取ConnectivityManager对象对应的NetworkInfo对象 + val networkInfo = connMgr.getNetworkInfo(networks[i]) + if (networkInfo != null && networkInfo.isConnected) { + if (networkInfo.type == ConnectivityManager.TYPE_WIFI) { + // connected to wifi + isConnected = true + //Toast.makeText(context, networkInfo.typeName, Toast.LENGTH_SHORT).show() + break@loop + } else if (networkInfo.type == ConnectivityManager.TYPE_MOBILE) { + // connected to mobile + isConnected = true + //Toast.makeText(context, networkInfo.typeName, Toast.LENGTH_SHORT).show() + break@loop + } + } } } } + } catch (e: Exception) { + e.printStackTrace() } if (isConnected) { sendMsg2Service(context) @@ -64,7 +68,7 @@ class NetWorkStateReceiver : BroadcastReceiver() { } private fun sendMsg2Service(context: Context) { - Toast.makeText(context, "Restart v2ray", Toast.LENGTH_SHORT).show() + //Toast.makeText(context, "Restart v2ray", Toast.LENGTH_SHORT).show() MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_RESTART_SOFT, "") } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 536ceec..43b18df 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -6,7 +6,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager -import android.net.ConnectivityManager +//import android.net.ConnectivityManager import android.net.VpnService import android.os.* import android.support.v4.app.NotificationCompat @@ -14,7 +14,7 @@ import com.orhanobut.logger.Logger import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.extension.defaultDPreference -import com.v2ray.ang.receiver.NetWorkStateReceiver +//import com.v2ray.ang.receiver.NetWorkStateReceiver import com.v2ray.ang.ui.MainActivity import com.v2ray.ang.ui.PerAppProxyActivity import com.v2ray.ang.ui.SettingsActivity @@ -40,7 +40,7 @@ class V2RayVpnService : VpnService() { private val v2rayPoint = Libv2ray.newV2RayPoint() private val v2rayCallback = V2RayCallback() // private var connectivitySubscription: Subscription? = null - private var netWorkStateReceiver: NetWorkStateReceiver? = null +// private var netWorkStateReceiver: NetWorkStateReceiver? = null private lateinit var configContent: String private lateinit var mInterface: ParcelFileDescriptor val fd: Int get() = mInterface.fd @@ -177,7 +177,7 @@ class V2RayVpnService : VpnService() { } unregisterReceiver(mMsgReceive) - unregisterReceiver(netWorkStateReceiver) +// unregisterReceiver(netWorkStateReceiver) // connectivitySubscription?.let { // it.unsubscribe() @@ -226,10 +226,10 @@ class V2RayVpnService : VpnService() { startForeground(NOTIFICATION_ID, notification) - if (netWorkStateReceiver == null) { - netWorkStateReceiver = NetWorkStateReceiver() - } - registerReceiver(netWorkStateReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) +// if (netWorkStateReceiver == null) { +// netWorkStateReceiver = NetWorkStateReceiver() +// } +// registerReceiver(netWorkStateReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) } private fun cancelNotification() { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 11d4a57..c3263dd 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -25,6 +25,7 @@ class SettingsActivity : BaseActivity() { const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland" // const val PREF_START_ON_BOOT = "pref_start_on_boot" const val PREF_PER_APP_PROXY = "pref_per_app_proxy" + const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_REMOTE_DNS = "pref_remote_dns" const val PREF_DONATE = "pref_donate" diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index a2809e4..8cfaa6a 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -135,6 +135,8 @@ object AngConfigManager { fun genStoreV2rayConfig(): Boolean { try { angConfig.bypassMainland = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_BYPASS_MAINLAND, false) + angConfig.muxEnabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) + val result = V2rayConfigUtil.getV2rayConfig(app, angConfig) if (result.status) { app.defaultDPreference.setPrefString(PREF_CURR_CONFIG, result.content) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 3d458e3..facd8c9 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -11,7 +11,6 @@ import com.v2ray.ang.ui.SettingsActivity import org.json.JSONArray import org.json.JSONException import org.json.JSONObject -import java.util.* object V2rayConfigUtil { private val lib2rayObj: JSONObject by lazy { @@ -49,7 +48,6 @@ object V2rayConfigUtil { }, "preparedDomainName": { "domainName": [ - ":<10086>" ], "tcpVersion": "tcp4", "udpVersion": "udp4" @@ -203,7 +201,7 @@ object V2rayConfigUtil { v2rayConfig.outbound.settings.vnext[0].users[0].security = vmess.security //Mux - v2rayConfig.outbound.mux.enabled = true + v2rayConfig.outbound.mux.enabled = config.muxEnabled //远程服务器底层传输配置 v2rayConfig.outbound.streamSettings = boundStreamSettings(config) @@ -298,15 +296,18 @@ object V2rayConfigUtil { try { //绕过大陆网址 if (config.bypassMainland) { - val rulesItem1 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", "", null, null, "") - rulesItem1.type = "chinasites" - rulesItem1.outboundTag = "direct" - v2rayConfig.routing.settings.rules.add(rulesItem1) - - val rulesItem2 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", "", null, null, "") - rulesItem2.type = "chinaip" - rulesItem2.outboundTag = "direct" - v2rayConfig.routing.settings.rules.add(rulesItem2) +// val rulesItem1 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", "", null, null, "") +// rulesItem1.type = "chinasites" +// rulesItem1.outboundTag = "direct" +// v2rayConfig.routing.settings.rules.add(rulesItem1) +// +// val rulesItem2 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", "", null, null, "") +// rulesItem2.type = "chinaip" +// rulesItem2.outboundTag = "direct" +// v2rayConfig.routing.settings.rules.add(rulesItem2) + + v2rayConfig.routing.settings.rules[0].domain?.add("geosite:cn") + v2rayConfig.routing.settings.rules[0].ip?.add("geoip:cn") } } catch (e: Exception) { e.printStackTrace() diff --git a/V2rayNG/app/src/main/res/values-zh/strings.xml b/V2rayNG/app/src/main/res/values-zh/strings.xml index 38962de..aa539d1 100644 --- a/V2rayNG/app/src/main/res/values-zh/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh/strings.xml @@ -68,6 +68,9 @@ 分应用代理 分应用代理仅支持 Android 5.0 Lollipop 及更高 + 开启Mux多路复用 + 开启可能会加速,关闭可能会减少断流 + 捐赠 支持开发者 陆续增加一些试验性的进阶功能 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index a5ae358..2a02638 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -69,6 +69,9 @@ Per-app proxy Per-app proxy mode only support Android 5.0 Lollipop or higher + Enable Mux + Enable maybe speed up network and switch network maybe flash + Donate Donate developer Add some experimental advanced features diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 6977d4f..0400ccb 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -11,6 +11,11 @@ android:summary="@string/summary_pref_per_app_proxy" android:title="@string/title_pref_per_app_proxy" /> + + Date: Fri, 1 Dec 2017 21:21:57 +0800 Subject: [PATCH 15/49] Create issue_template.md --- .github/issue_template.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/issue_template.md diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..be1d9bb --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,26 @@ +在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。填写完问题模板后请删除提示,否则可能会被直接关闭问题。 + +### 预期行为 +描述你认为应该发生什么 + +### 实际行为 +描述实际发生了什么 + +### 复现方法 +1. +2. +3. + +### 日志信息 +

+ +通过 `adb logcat com.v2ray.ang` 获取日志。请自行删减日志中可能出现的敏感信息。 +``` +在这里粘贴日志 +``` +
+ +### 环境信息 + +### 额外信息(可选) + From c4115cd1338b7ca35d5193e8ae7ae22eee0a09cf Mon Sep 17 00:00:00 2001 From: 2dust Date: Thu, 7 Dec 2017 16:01:33 +0800 Subject: [PATCH 16/49] regular update --- V2rayNG/app/src/main/AndroidManifest.xml | 33 +++++-- .../java/com/v2ray/ang/util/IabHelper.java | 10 +- .../kotlin/com/v2ray/ang/AngApplication.kt | 5 +- .../main/kotlin/com/v2ray/ang/AppConfig.kt | 7 ++ .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 2 +- .../com/v2ray/ang/receiver/TaskerReceiver.kt | 32 ++++++ .../com/v2ray/ang/receiver/WidgetProvider.kt | 47 +-------- .../com/v2ray/ang/service/QSTileService.kt | 8 +- .../com/v2ray/ang/service/V2RayVpnService.kt | 72 +++++++++++--- .../kotlin/com/v2ray/ang/ui/LogcatActivity.kt | 66 +++++++++++++ .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 16 ++- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 9 +- .../com/v2ray/ang/ui/PerAppProxyActivity.kt | 24 +---- .../com/v2ray/ang/ui/SettingsActivity.kt | 20 ++-- .../kotlin/com/v2ray/ang/ui/TaskerActivity.kt | 99 +++++++++++++++++++ .../com/v2ray/ang/util/AngConfigManager.kt | 23 ++++- .../main/kotlin/com/v2ray/ang/util/Utils.kt | 54 ++++++++-- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 20 +++- .../src/main/res/drawable/ic_copy_white.xml | 9 ++ .../res/drawable/ic_logcat_white_24dp.xml | 9 ++ .../main/res/layout/activity_bypass_list.xml | 15 ++- .../src/main/res/layout/activity_logcat.xml | 28 ++++++ .../src/main/res/layout/activity_tasker.xml | 48 +++++++++ V2rayNG/app/src/main/res/menu/menu_logcat.xml | 9 ++ V2rayNG/app/src/main/res/menu/menu_main.xml | 7 +- .../app/src/main/res/values-zh/strings.xml | 9 +- V2rayNG/app/src/main/res/values/strings.xml | 9 +- .../app/src/main/res/xml/pref_settings.xml | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- 29 files changed, 547 insertions(+), 153 deletions(-) create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt create mode 100644 V2rayNG/app/src/main/res/drawable/ic_copy_white.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_logcat_white_24dp.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_logcat.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_tasker.xml create mode 100644 V2rayNG/app/src/main/res/menu/menu_logcat.xml diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index f081cd9..a51080d 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ - + - - + + - + + + --> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java index e58ae6e..911d20d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java @@ -189,7 +189,7 @@ public interface OnIabSetupFinishedListener { * * @param result The result of the setup process. */ - public void onIabSetupFinished(IabResult result); + void onIabSetupFinished(IabResult result); } /** @@ -323,7 +323,7 @@ public interface OnIabPurchaseFinishedListener { * @param result The result of the purchase. * @param info The purchase information (null if purchase failed) */ - public void onIabPurchaseFinished(IabResult result, Purchase info); + void onIabPurchaseFinished(IabResult result, Purchase info); } // The listener registered on launchPurchaseFlow, which we have to call back when @@ -584,7 +584,7 @@ public interface QueryInventoryFinishedListener { * @param result The result of the operation. * @param inv The inventory. */ - public void onQueryInventoryFinished(IabResult result, Inventory inv); + void onQueryInventoryFinished(IabResult result, Inventory inv); } @@ -689,7 +689,7 @@ public interface OnConsumeFinishedListener { * @param purchase The purchase that was (or was to be) consumed. * @param result The result of the consumption operation. */ - public void onConsumeFinished(Purchase purchase, IabResult result); + void onConsumeFinished(Purchase purchase, IabResult result); } /** @@ -703,7 +703,7 @@ public interface OnConsumeMultiFinishedListener { * @param results The results of each consumption operation, corresponding to each * sku. */ - public void onConsumeMultiFinished(List purchases, List results); + void onConsumeMultiFinished(List purchases, List results); } /** diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt index 0b0eabe..ee5074c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt @@ -1,9 +1,6 @@ package com.v2ray.ang import android.app.Application -import android.content.Context -import com.orhanobut.logger.LogLevel -import com.orhanobut.logger.Logger import com.squareup.leakcanary.LeakCanary import com.v2ray.ang.util.AngConfigManager import me.dozen.dpreference.DPreference @@ -28,7 +25,7 @@ class AngApplication : Application() { if (firstRun) defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply() - Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE) + //Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE) AngConfigManager.inject(this) } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt index fb0e309..a254e60 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt @@ -15,6 +15,11 @@ object AppConfig { const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity" const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click" + 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 MSG_REGISTER_CLIENT = 1 const val MSG_STATE_RUNNING = 11 const val MSG_STATE_NOT_RUNNING = 12 @@ -26,4 +31,6 @@ object AppConfig { const val MSG_STATE_STOP_SUCCESS = 41 const val MSG_STATE_RESTART = 5 const val MSG_STATE_RESTART_SOFT = 6 + + } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt index 7150b7f..93372b2 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt @@ -92,7 +92,7 @@ data class V2rayConfig(val port: Int, var rules: ArrayList) { data class RulesBean(var type: String, - var port: String, + //var port: String, var ip: ArrayList?, var domain: ArrayList?, var outboundTag: String) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt new file mode 100644 index 0000000..5baab3b --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt @@ -0,0 +1,32 @@ +package com.v2ray.ang.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.google.zxing.WriterException +import com.v2ray.ang.AppConfig + +import com.v2ray.ang.util.Utils + +class TaskerReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent?) { + + try { + val bundle = intent?.getBundleExtra(AppConfig.TASKER_EXTRA_BUNDLE) + val switch = bundle?.getBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, false) + val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, "") + + if (switch == null || guid == null) { + return + } else if (switch) { + Utils.startVService(context, guid) + } else { + Utils.stopVService(context) + } + } catch (e: WriterException) { + e.printStackTrace() + + } + + } +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt index aa99d60..de56ce8 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt @@ -9,9 +9,6 @@ import android.os.Bundle import android.widget.RemoteViews import com.v2ray.ang.R import com.v2ray.ang.AppConfig -import com.v2ray.ang.service.V2RayVpnService -import com.v2ray.ang.util.AngConfigManager -import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.Utils import org.jetbrains.anko.toast @@ -38,50 +35,16 @@ class WidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) { - if (Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")) { - MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "") + + val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService") + if (isRunning) { context.toast(R.string.toast_services_stop) + Utils.stopVService(context) } else { context.toast(R.string.toast_services_start) - if (AngConfigManager.genStoreV2rayConfig()) { - V2RayVpnService.startV2Ray(context) - } + Utils.startVService(context) } } } - /** - * 每删除一次窗口小部件就调用一次 - */ - override fun onDeleted(context: Context, appWidgetIds: IntArray) { - super.onDeleted(context, appWidgetIds) - } - - /** - * 当最后一个该窗口小部件删除时调用该方法 - */ - override fun onDisabled(context: Context) { - super.onDisabled(context) - } - - /** - * 当该窗口小部件第一次添加到桌面时调用该方法 - */ - override fun onEnabled(context: Context) { - super.onEnabled(context) - } - - /** - * 当小部件大小改变时 - */ - override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle) { - super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) - } - - /** - * 当小部件从备份恢复时调用该方法 - */ - override fun onRestored(context: Context, oldWidgetIds: IntArray, newWidgetIds: IntArray) { - super.onRestored(context, oldWidgetIds, newWidgetIds) - } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt index b714366..c7994f3 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt @@ -13,8 +13,8 @@ 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.AngConfigManager import com.v2ray.ang.util.MessageUtil +import com.v2ray.ang.util.Utils import org.jetbrains.anko.toast import java.lang.ref.SoftReference @@ -58,14 +58,12 @@ class QSTileService : TileService() { Tile.STATE_INACTIVE -> { val intent = VpnService.prepare(this) if (intent == null) - if (AngConfigManager.genStoreV2rayConfig()) { - V2RayVpnService.startV2Ray(this) - } else { + if (!Utils.startVService(this)) { toast(R.string.app_tile_first_use) } } Tile.STATE_ACTIVE -> { - MessageUtil.sendMsg2Service(this, AppConfig.MSG_STATE_STOP, "") + Utils.stopVService(this) } } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 43b18df..7ccfdfc 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -1,16 +1,20 @@ 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.content.pm.PackageManager +import android.graphics.Color //import android.net.ConnectivityManager import android.net.VpnService import android.os.* +import android.support.annotation.RequiresApi import android.support.v4.app.NotificationCompat -import com.orhanobut.logger.Logger import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.extension.defaultDPreference @@ -92,7 +96,7 @@ class V2RayVpnService : VpnService() { else builder.addAllowedApplication(it) } catch (e: PackageManager.NameNotFoundException) { - Logger.d(e) + //Logger.d(e) } } } @@ -110,9 +114,10 @@ class V2RayVpnService : VpnService() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - startV2ray() + restartV2Ray() - return super.onStartCommand(intent, flags, startId) + return START_STICKY + //return super.onStartCommand(intent, flags, startId) } private fun vpnCheckIsReady() { @@ -131,14 +136,16 @@ class V2RayVpnService : VpnService() { } } - private fun startV2ray() { - if (!v2rayPoint.isRunning) { + private fun startV2ray(isForced: Boolean = false) { + if (!v2rayPoint.isRunning || isForced) { - registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)) + try { + registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)) + } catch (e: Exception) { + } configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") - // connectivitySubscription = ReactiveNetwork.observeNetworkConnectivity(this.applicationContext) // .subscribeOn(Schedulers.io()) // //.filter(Connectivity.hasState(NetworkInfo.State.CONNECTED)) @@ -166,7 +173,7 @@ class V2RayVpnService : VpnService() { // showNotification() } - private fun stopV2Ray() { + private fun stopV2Ray(isForced: Boolean = true) { // val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "") // val emptyInfo = VpnNetworkInfo() // val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo) @@ -176,18 +183,27 @@ class V2RayVpnService : VpnService() { v2rayPoint.stopLoop() } - unregisterReceiver(mMsgReceive) // unregisterReceiver(netWorkStateReceiver) // connectivitySubscription?.let { // it.unsubscribe() // connectivitySubscription = null // } - - MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "") cancelNotification() - stopSelf() + + if (isForced) { + try { + unregisterReceiver(mMsgReceive) + } catch (e: Exception) { + } + stopSelf() + } + } + + private fun restartV2Ray() { + stopV2Ray(false) + startV2ray(true) } private fun restartV2RaySoft() { @@ -213,7 +229,16 @@ class V2RayVpnService : VpnService() { NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, PendingIntent.FLAG_UPDATE_CURRENT) - val notification = NotificationCompat.Builder(applicationContext, "M_CH_ID") + 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) + "" + } + + val notification = NotificationCompat.Builder(applicationContext, channelId) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) .setContentText(getString(R.string.notification_action_more)) @@ -232,6 +257,20 @@ class V2RayVpnService : VpnService() { // registerReceiver(netWorkStateReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) } + @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 + val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + service.createNotificationChannel(chan) + return channelId + } + private fun cancelNotification() { stopForeground(true) } @@ -249,7 +288,7 @@ class V2RayVpnService : VpnService() { 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) + //Logger.d(s) return 0 } @@ -293,6 +332,9 @@ class V2RayVpnService : VpnService() { AppConfig.MSG_STATE_STOP -> { vpnService?.stopV2Ray() } + AppConfig.MSG_STATE_RESTART -> { + vpnService?.restartV2Ray() + } AppConfig.MSG_STATE_RESTART_SOFT -> { vpnService?.restartV2RaySoft() } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt new file mode 100644 index 0000000..123bc74 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt @@ -0,0 +1,66 @@ +package com.v2ray.ang.ui + +import android.os.Bundle +import android.text.method.ScrollingMovementMethod +import android.view.Menu +import android.view.MenuItem +import com.v2ray.ang.R +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 java.io.IOException +import java.util.LinkedHashSet + +class LogcatActivity : BaseActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_logcat) + + title = getString(R.string.title_logcat) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onPostResume() { + super.onPostResume() + try { + doAsync { + val lst = LinkedHashSet() + lst.add("logcat") + lst.add("-d") + lst.add("-v") + lst.add("time") + lst.add("-s") + lst.add("GoLog:I") + val process = Runtime.getRuntime().exec(lst.toTypedArray()) +// val bufferedReader = BufferedReader( +// InputStreamReader(process.inputStream)) +// val allText = bufferedReader.use(BufferedReader::readText) + val allText = process.inputStream.bufferedReader().use { it.readText() } + uiThread { + tv_logcat.text = allText + tv_logcat.movementMethod = ScrollingMovementMethod() + } + } + } catch (e: IOException) { + } + } + + 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()) + toast(R.string.toast_success) + true + } + + else -> super.onOptionsItemSelected(item) + } +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 1555c79..5c54684 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -2,7 +2,6 @@ package com.v2ray.ang.ui import android.Manifest import android.content.* -import android.net.ConnectivityManager import android.net.Uri import android.net.VpnService import android.support.v7.widget.LinearLayoutManager @@ -10,7 +9,6 @@ import android.view.Menu import android.view.MenuItem import com.tbruyelle.rxpermissions.RxPermissions import com.v2ray.ang.R -import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.Utils import kotlinx.android.synthetic.main.activity_main.* @@ -52,7 +50,7 @@ class MainActivity : BaseActivity() { fab.setOnClickListener { if (fabChecked) { - MessageUtil.sendMsg2Service(this, AppConfig.MSG_STATE_STOP, "") + Utils.stopVService(this) } else { val intent = VpnService.prepare(this) if (intent == null) { @@ -69,9 +67,7 @@ class MainActivity : BaseActivity() { fun startV2Ray() { toast(R.string.toast_services_start) - if (AngConfigManager.genStoreV2rayConfig()) { - V2RayVpnService.startV2Ray(this) - } + Utils.startVService(this) } override fun onStart() { @@ -162,14 +158,14 @@ class MainActivity : BaseActivity() { startActivity("isRunning" to fabChecked) true } + R.id.logcat -> { + startActivity() + true + } else -> super.onOptionsItemSelected(item) } - override fun onBackPressed() { - super.onBackPressed() - } - /** * import config from qrcode */ diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 2373cbf..1cd2db2 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -9,6 +9,7 @@ import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.extension.alertView import com.v2ray.ang.extension.selector import com.v2ray.ang.util.AngConfigManager +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.* @@ -60,8 +61,7 @@ class MainRecyclerAdapter(val activity: BaseActivity) : RecyclerView.Adapter + mActivity.selector(null, share_method.asList()) { i -> try { when (i) { 0 -> { @@ -98,8 +98,11 @@ class MainRecyclerAdapter(val activity: BaseActivity) : RecyclerView.Adapter defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked) } - 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) - tv_bypass_apps.setText(if (isChecked) R.string.switch_bypass_apps_on else - R.string.switch_bypass_apps_off) } - switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false) - tv_bypass_apps.setText(if (switch_bypass_apps.isChecked) R.string.switch_bypass_apps_on else - R.string.switch_bypass_apps_off) - - container_per_app_proxy.setOnClickListener { - switch_bypass_apps.performClick() - } - - container_bypass_apps.setOnClickListener { - switch_bypass_apps.performClick() - } - et_search.setOnEditorActionListener() { v, actionId, event -> + 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 diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index c3263dd..8efecd8 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -8,13 +8,11 @@ import android.preference.CheckBoxPreference import android.preference.EditTextPreference import android.preference.Preference import android.preference.PreferenceFragment -import android.support.v7.app.AppCompatActivity import com.v2ray.ang.BuildConfig import com.v2ray.ang.InappBuyActivity import com.v2ray.ang.R import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.onClick -import de.psdev.licensesdialog.LicensesDialogFragment import libv2ray.Libv2ray import org.jetbrains.anko.act import org.jetbrains.anko.defaultSharedPreferences @@ -29,7 +27,7 @@ class SettingsActivity : BaseActivity() { const val PREF_REMOTE_DNS = "pref_remote_dns" const val PREF_DONATE = "pref_donate" - const val PREF_LICENSES = "pref_licenses" +// const val PREF_LICENSES = "pref_licenses" const val PREF_FEEDBACK = "pref_feedback" const val PREF_VERSION = "pref_version" // const val PREF_AUTO_RESTART = "pref_auto_restart" @@ -48,7 +46,7 @@ class SettingsActivity : BaseActivity() { val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } val donate: Preference by lazy { findPreference(PREF_DONATE) } - val licenses: Preference by lazy { findPreference(PREF_LICENSES) } +// val licenses: Preference by lazy { findPreference(PREF_LICENSES) } val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } val version: Preference by lazy { findPreference(PREF_VERSION) } @@ -60,13 +58,13 @@ class SettingsActivity : BaseActivity() { donate() } - licenses.onClick { - val fragment = LicensesDialogFragment.Builder(act) - .setNotices(R.raw.licenses) - .setIncludeOwnLicense(false) - .build() - fragment.show((act as AppCompatActivity).supportFragmentManager, null) - } +// licenses.onClick { +// val fragment = LicensesDialogFragment.Builder(act) +// .setNotices(R.raw.licenses) +// .setIncludeOwnLicense(false) +// .build() +// fragment.show((act as AppCompatActivity).supportFragmentManager, null) +// } feedback.onClick { openUri("https://github.com/2dust/v2rayNG/issues") diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt new file mode 100644 index 0000000..8d1bdad --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt @@ -0,0 +1,99 @@ +package com.v2ray.ang.ui + +import android.app.Activity +import android.os.Bundle +import android.view.View +import android.widget.ArrayAdapter +import android.widget.ListView +import java.util.ArrayList +import com.v2ray.ang.R +import com.v2ray.ang.util.AngConfigManager +import android.content.Intent +import android.view.Menu +import android.view.MenuItem +import com.google.zxing.WriterException +import com.v2ray.ang.AppConfig +import kotlinx.android.synthetic.main.activity_tasker.* + + +class TaskerActivity : BaseActivity() { + private var listview: ListView? = null + private var lstData: ArrayList = ArrayList() + private val vmess = AngConfigManager.configs.vmess + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_tasker) + + vmess.forEach { + lstData.add(it.remarks) + } + val adapter = ArrayAdapter(this, + android.R.layout.simple_list_item_single_choice, lstData) + listview = findViewById(R.id.listview) as ListView + listview!!.adapter = adapter + + init() + } + + private fun init() { + try { + val bundle = intent?.getBundleExtra(AppConfig.TASKER_EXTRA_BUNDLE) + val switch = bundle?.getBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, false) + val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, "") + + if (switch == null || guid == null) { + return + } else { + switch_start_service.isChecked = switch + } + } catch (e: WriterException) { + e.printStackTrace() + + } + } + + private fun confirmFinish() { + val position = listview?.checkedItemPosition + if (position == null || position < 0) { + return + } + + val extraBundle = Bundle() + extraBundle.putBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, switch_start_service.isChecked) + extraBundle.putString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, vmess[position].guid) + val intent = Intent() + + var remarks = vmess[position].remarks + if (switch_start_service.isChecked) { + remarks = "Start $remarks" + } else { + remarks = "Stop $remarks" + } + + intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle) + intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, remarks) + setResult(Activity.RESULT_OK, intent) + finish() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.action_server, menu) + val del_config = menu?.findItem(R.id.del_config) + del_config?.isVisible = false + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.del_config -> { + true + } + R.id.save_config -> { + confirmFinish() + true + } + else -> super.onOptionsItemSelected(item) + } + +} + diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 8cfaa6a..8805484 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -258,7 +258,7 @@ object AngConfigManager { return -1 } - Utils.setClipboard(conf, app.applicationContext) + Utils.setClipboard(app.applicationContext, conf) } catch (e: Exception) { e.printStackTrace() @@ -326,4 +326,25 @@ object AngConfigManager { } return 0 } + + /** + * getIndexViaGuid + */ + fun getIndexViaGuid(guid: String): Int { + try { + if (TextUtils.isEmpty(guid)) { + return -1 + } + for (i in angConfig.vmess.indices) { + if (angConfig.vmess[i].guid == guid) { + return i + } + } + return -1 + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index ab11e6c..3e16597 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -12,11 +12,11 @@ import com.google.zxing.EncodeHintType import java.util.* import kotlin.collections.HashMap import android.app.ActivityManager +import android.content.ClipData import android.util.Patterns import android.webkit.URLUtil -import com.orhanobut.logger.Logger -import java.util.logging.LogManager - +import com.v2ray.ang.AppConfig +import com.v2ray.ang.service.V2RayVpnService object Utils { @@ -60,7 +60,7 @@ object Utils { fun getClipboard(context: Context): String { try { val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - return cmb.text.toString().trim() + return cmb.primaryClip?.getItemAt(0)?.text.toString() } catch (e: Exception) { e.printStackTrace() return "" @@ -70,10 +70,11 @@ object Utils { /** * set text to clipboard */ - fun setClipboard(content: String, context: Context) { + fun setClipboard(context: Context, content: String) { try { val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - cmb.text = content + val clipData = ClipData.newPlainText(null, content) + cmb.primaryClip = clipData } catch (e: Exception) { e.printStackTrace() } @@ -205,12 +206,12 @@ object Utils { * * * @return true 在运行 false 不在运行 */ - fun isServiceRun(mContext: Context, className: String): Boolean { + fun isServiceRun(context: Context, className: String): Boolean { var isRun = false - val activityManager = mContext + val activityManager = context .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val serviceList = activityManager - .getRunningServices(900) + .getRunningServices(999) val size = serviceList.size for (i in 0..size - 1) { if (serviceList[i].service.className == className) { @@ -220,6 +221,41 @@ object Utils { } return isRun } + + /** + * startVService + */ + fun startVService(context: Context): Boolean { + if (AngConfigManager.genStoreV2rayConfig()) { + V2RayVpnService.startV2Ray(context) + return true + } else { + return false + } + } + + /** + * startVService + */ + fun startVService(context: Context, guid: String): Boolean { + val index = AngConfigManager.getIndexViaGuid(guid) + return startVService(context, index) + } + + /** + * startVService + */ + fun startVService(context: Context, index: Int): Boolean { + AngConfigManager.setActiveServer(index) + return startVService(context) + } + + /** + * stopVService + */ + fun stopVService(context: Context) { + MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "") + } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index facd8c9..5066f4d 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -171,7 +171,7 @@ object V2rayConfigUtil { return result } val vmess = config.vmess[config.index] - val guid = vmess.guid; + val guid = vmess.guid val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "") //增加lib2ray @@ -306,8 +306,22 @@ object V2rayConfigUtil { // rulesItem2.outboundTag = "direct" // v2rayConfig.routing.settings.rules.add(rulesItem2) - v2rayConfig.routing.settings.rules[0].domain?.add("geosite:cn") - v2rayConfig.routing.settings.rules[0].ip?.add("geoip:cn") +// v2rayConfig.routing.settings.rules[0].domain?.add("geosite:cn") +// v2rayConfig.routing.settings.rules[0].ip?.add("geoip:cn") + + val rulesItem1 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") + rulesItem1.type = "field" + rulesItem1.outboundTag = "direct" + rulesItem1.domain = ArrayList() + rulesItem1.domain?.add("geosite:cn") + v2rayConfig.routing.settings.rules.add(rulesItem1) + + val rulesItem2 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") + rulesItem2.type = "field" + rulesItem2.outboundTag = "direct" + rulesItem2.ip = ArrayList() + rulesItem2.ip?.add("geoip:cn") + v2rayConfig.routing.settings.rules.add(rulesItem2) } } catch (e: Exception) { e.printStackTrace() diff --git a/V2rayNG/app/src/main/res/drawable/ic_copy_white.xml b/V2rayNG/app/src/main/res/drawable/ic_copy_white.xml new file mode 100644 index 0000000..e50927b --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_copy_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_logcat_white_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_logcat_white_24dp.xml new file mode 100644 index 0000000..e7d3eb3 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_logcat_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml b/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml index 636fb58..54c5c0c 100644 --- a/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml +++ b/V2rayNG/app/src/main/res/layout/activity_bypass_list.xml @@ -5,6 +5,14 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + android:text="@string/title_pref_per_app_proxy" + android:textAppearance="@style/TextAppearance.AppCompat.Medium" /> @@ -70,7 +79,9 @@ android:layout_alignParentStart="true" android:layout_centerVertical="true" android:layout_toLeftOf="@id/switch_bypass_apps" - android:layout_toStartOf="@id/switch_bypass_apps" /> + android:layout_toStartOf="@id/switch_bypass_apps" + android:text="@string/switch_bypass_apps_mode" + android:textAppearance="@style/TextAppearance.AppCompat.Medium" /> diff --git a/V2rayNG/app/src/main/res/layout/activity_logcat.xml b/V2rayNG/app/src/main/res/layout/activity_logcat.xml new file mode 100644 index 0000000..2e9042a --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_logcat.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_tasker.xml b/V2rayNG/app/src/main/res/layout/activity_tasker.xml new file mode 100644 index 0000000..f266189 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_tasker.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_logcat.xml b/V2rayNG/app/src/main/res/menu/menu_logcat.xml new file mode 100644 index 0000000..cabe6c2 --- /dev/null +++ b/V2rayNG/app/src/main/res/menu/menu_logcat.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_main.xml b/V2rayNG/app/src/main/res/menu/menu_main.xml index 4667775..e43b464 100644 --- a/V2rayNG/app/src/main/res/menu/menu_main.xml +++ b/V2rayNG/app/src/main/res/menu/menu_main.xml @@ -37,5 +37,10 @@ android:id="@+id/settings" android:icon="@drawable/ic_settings_white_24dp" android:title="@string/title_settings" - app:showAsAction="ifRoom" /> + app:showAsAction="never" /> + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-zh/strings.xml b/V2rayNG/app/src/main/res/values-zh/strings.xml index aa539d1..ea4ec40 100644 --- a/V2rayNG/app/src/main/res/values-zh/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh/strings.xml @@ -53,9 +53,8 @@ 正在加载 全选 - 选中的应用不走代理 - 选中的应用走代理 输入关键字 + 绕行模式 设置 @@ -90,6 +89,12 @@ 感谢您的捐赠! 捐赠 + Logcat + 复制 + + 启动服务 + 确定 + 二维码 导出至剪贴板 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 2a02638..bfec840 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -53,9 +53,8 @@ Loading Select all - Don\'t proxy selected apps - Proxy selected apps Enter keywords + Bypass Mode @@ -91,6 +90,12 @@ Thank you for your donation! Donate + Logcat + Copy + + Start Service + Confirm + QRcode Export to clipboard diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 0400ccb..886440e 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -33,9 +33,9 @@ - + + + Date: Tue, 26 Dec 2017 15:21:13 +0800 Subject: [PATCH 17/49] update --- V2rayNG/app/src/main/AndroidManifest.xml | 3 + V2rayNG/app/src/main/assets/v2ray_config.json | 23 +++- .../main/kotlin/com/v2ray/ang/AppConfig.kt | 9 +- .../com/v2ray/ang/service/V2RayVpnService.kt | 16 ++- .../com/v2ray/ang/ui/FragmentAdapter.kt | 21 ++++ .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 39 ++++-- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 15 ++- .../v2ray/ang/ui/RoutingSettingsActivity.kt | 33 +++++ .../v2ray/ang/ui/RoutingSettingsFragment.kt | 115 ++++++++++++++++++ .../com/v2ray/ang/ui/Server2Activity.kt | 1 - .../kotlin/com/v2ray/ang/ui/ServerActivity.kt | 1 - .../com/v2ray/ang/ui/SettingsActivity.kt | 17 ++- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 77 ++++++++++-- V2rayNG/app/src/main/res/drawable/ic_v.xml | 9 ++ .../app/src/main/res/layout/activity_main.xml | 24 ++-- .../res/layout/activity_routing_settings.xml | 17 +++ .../res/layout/fragment_routing_settings.xml | 39 ++++++ .../app/src/main/res/menu/menu_routing.xml | 26 ++++ .../app/src/main/res/values-zh/strings.xml | 21 ++++ V2rayNG/app/src/main/res/values/strings.xml | 19 +++ .../app/src/main/res/xml/pref_settings.xml | 14 ++- 21 files changed, 490 insertions(+), 49 deletions(-) create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/FragmentAdapter.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt create mode 100644 V2rayNG/app/src/main/res/drawable/ic_v.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_routing_settings.xml create mode 100644 V2rayNG/app/src/main/res/layout/fragment_routing_settings.xml create mode 100644 V2rayNG/app/src/main/res/menu/menu_routing.xml diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index a51080d..d5725ad 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -47,6 +47,9 @@ + , private val mTitles: List) : FragmentStatePagerAdapter(fm) { + + override fun getItem(position: Int): Fragment { + return mFragments[position] + } + + override fun getCount(): Int { + return mFragments.size + } + + override fun getPageTitle(position: Int): CharSequence? { + return mTitles[position] + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 5c54684..88f15f6 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -22,6 +22,9 @@ import org.jetbrains.anko.* import java.lang.ref.SoftReference import java.net.URL import android.content.IntentFilter +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import java.util.concurrent.TimeUnit class MainActivity : BaseActivity() { companion object { @@ -31,12 +34,13 @@ class MainActivity : BaseActivity() { private const val REQUEST_SCAN_URL = 3 } - var fabChecked = false + var isRunning = false set(value) { field = value adapter.changeable = !value if (value) { fab.imageResource = R.drawable.ic_start_connected + hideCircle() } else { fab.imageResource = R.drawable.ic_start_idle } @@ -49,7 +53,7 @@ class MainActivity : BaseActivity() { setContentView(R.layout.activity_main) fab.setOnClickListener { - if (fabChecked) { + if (isRunning) { Utils.stopVService(this) } else { val intent = VpnService.prepare(this) @@ -66,13 +70,15 @@ class MainActivity : BaseActivity() { } fun startV2Ray() { + fabProgressCircle?.show() + toast(R.string.toast_services_start) Utils.startVService(this) } override fun onStart() { super.onStart() - fabChecked = false + isRunning = false // val intent = Intent(this.applicationContext, V2RayVpnService::class.java) // intent.`package` = AppConfig.ANG_PACKAGE @@ -138,7 +144,7 @@ class MainActivity : BaseActivity() { true } R.id.import_manually -> { - startActivity("position" to -1, "isRunning" to fabChecked) + startActivity("position" to -1, "isRunning" to isRunning) adapter.updateConfigList() true } @@ -155,7 +161,7 @@ class MainActivity : BaseActivity() { true } R.id.settings -> { - startActivity("isRunning" to fabChecked) + startActivity("isRunning" to isRunning) true } R.id.logcat -> { @@ -337,21 +343,21 @@ class MainActivity : BaseActivity() { val activity = mReference.get() when (intent?.getIntExtra("key", 0)) { AppConfig.MSG_STATE_RUNNING -> { - activity?.fabChecked = true + activity?.isRunning = true } AppConfig.MSG_STATE_NOT_RUNNING -> { - activity?.fabChecked = false + activity?.isRunning = false } AppConfig.MSG_STATE_START_SUCCESS -> { activity?.toast(R.string.toast_services_success) - activity?.fabChecked = true + activity?.isRunning = true } AppConfig.MSG_STATE_START_FAILURE -> { activity?.toast(R.string.toast_services_failure) - activity?.fabChecked = false + activity?.isRunning = false } AppConfig.MSG_STATE_STOP_SUCCESS -> { - activity?.fabChecked = false + activity?.isRunning = false } } } @@ -364,4 +370,17 @@ class MainActivity : BaseActivity() { } return super.onKeyDown(keyCode, event) } + + fun hideCircle() { + try { + Observable.timer(1, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + if (fabProgressCircle.isShown) { + fabProgressCircle.hide() + } + } + } catch (e: Exception) { + } + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 1cd2db2..9451921 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -6,8 +6,6 @@ import android.view.View import android.view.ViewGroup import com.v2ray.ang.R import com.v2ray.ang.dto.AngConfig -import com.v2ray.ang.extension.alertView -import com.v2ray.ang.extension.selector import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.Utils import kotlinx.android.synthetic.main.item_qrcode.view.* @@ -61,15 +59,20 @@ class MainRecyclerAdapter(val activity: BaseActivity) : RecyclerView.Adapter + mActivity.selector(null, share_method.asList()) { dialogInterface, i -> try { when (i) { 0 -> { val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null) iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position)) - mActivity.alertView("", iv) { - show() - } + + mActivity.alert { + customView { + linearLayout { + addView(iv) + } + } + }.show() } 1 -> { if (AngConfigManager.share2Clipboard(position) == 0) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt new file mode 100644 index 0000000..43f5ae7 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt @@ -0,0 +1,33 @@ +package com.v2ray.ang.ui + +import android.graphics.Color +import android.os.Bundle +import com.v2ray.ang.R +import android.support.v4.app.Fragment +import com.v2ray.ang.AppConfig +import kotlinx.android.synthetic.main.activity_routing_settings.* + + +class RoutingSettingsActivity : BaseActivity() { + private val titles: Array by lazy { + resources.getStringArray(R.array.routing_tag) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_routing_settings) + + title = getString(R.string.routing_settings_title) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + val fragments = ArrayList() + fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_AGENT)) + fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_DIRECT)) + fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)) + + val adapter = FragmentAdapter(supportFragmentManager, fragments, titles.toList()) + viewpager?.adapter = adapter + tablayout.setTabTextColors(Color.BLACK, Color.RED) + tablayout.setupWithViewPager(viewpager) + } +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt new file mode 100644 index 0000000..7a1d479 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt @@ -0,0 +1,115 @@ +package com.v2ray.ang.ui + + +import android.Manifest +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.os.Bundle +import android.support.v4.app.Fragment +import android.text.TextUtils +import android.view.* +import com.v2ray.ang.R +import com.v2ray.ang.extension.defaultDPreference +import com.v2ray.ang.util.Utils +import kotlinx.android.synthetic.main.fragment_routing_settings.* +import org.jetbrains.anko.toast +import android.view.MenuInflater +import com.tbruyelle.rxpermissions.RxPermissions +import org.jetbrains.anko.startActivityForResult +import org.jetbrains.anko.support.v4.startActivityForResult + + +class RoutingSettingsFragment : Fragment() { + companion object { + private const val routing_arg = "routing_arg" + private const val REQUEST_SCAN_REPLACE = 11 + private const val REQUEST_SCAN_APPEND = 12 + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_routing_settings, container, false) + } + + fun newInstance(arg: String): Fragment { + val fragment = RoutingSettingsFragment() + val bundle = Bundle() + bundle.putString(routing_arg, arg) + fragment.arguments = bundle + return fragment + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + val content = activity?.defaultDPreference?.getPrefString(arguments!!.getString(routing_arg), "") + et_routing_content.text = Utils.getEditable(content!!) + + setHasOptionsMenu(true) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_routing, menu) + return super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.save_routing -> { + val content = et_routing_content.text.toString() + activity?.defaultDPreference?.setPrefString(arguments!!.getString(routing_arg), content) + activity?.toast(R.string.toast_success) + true + } + R.id.del_routing -> { + et_routing_content.text = null + true + } + R.id.scan_replace -> { + scanQRcode(REQUEST_SCAN_REPLACE) + true + } + R.id.scan_append -> { + scanQRcode(REQUEST_SCAN_APPEND) + true + } + + else -> super.onOptionsItemSelected(item) + } + + fun scanQRcode(requestCode: Int): 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.getInstance(activity) + .request(Manifest.permission.CAMERA) + .subscribe { + if (it) + startActivityForResult(requestCode) + else + activity?.toast(R.string.toast_permission_denied) + } + } + return true + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + REQUEST_SCAN_REPLACE -> + if (resultCode == RESULT_OK) { + val content = data?.getStringExtra("SCAN_RESULT") + et_routing_content.text = Utils.getEditable(content!!) + } + REQUEST_SCAN_APPEND -> + if (resultCode == RESULT_OK) { + val content = data?.getStringExtra("SCAN_RESULT") + et_routing_content.text = Utils.getEditable("${et_routing_content.text},$content") + } + } + } + + +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt index b90a318..e19ca29 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt @@ -10,7 +10,6 @@ import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.Utils -import com.v2ray.ang.extension.alert import kotlinx.android.synthetic.main.activity_server2.* import org.jetbrains.anko.* diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt index 98d53f5..e017ad9 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt @@ -9,7 +9,6 @@ import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.Utils import kotlinx.android.synthetic.main.activity_server.* -import com.v2ray.ang.extension.alert import org.jetbrains.anko.* diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 8efecd8..15264ff 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -25,9 +25,11 @@ class SettingsActivity : BaseActivity() { const val PREF_PER_APP_PROXY = "pref_per_app_proxy" const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_REMOTE_DNS = "pref_remote_dns" + const val PREF_SPEEDUP_DOMAIN = "pref_speedup_domain" + const val PREF_ROUTING = "pref_routing" const val PREF_DONATE = "pref_donate" -// const val PREF_LICENSES = "pref_licenses" + // const val PREF_LICENSES = "pref_licenses" const val PREF_FEEDBACK = "pref_feedback" const val PREF_VERSION = "pref_version" // const val PREF_AUTO_RESTART = "pref_auto_restart" @@ -45,8 +47,9 @@ class SettingsActivity : BaseActivity() { // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } + val routing: Preference by lazy { findPreference(PREF_ROUTING) } val donate: Preference by lazy { findPreference(PREF_DONATE) } -// val licenses: Preference by lazy { findPreference(PREF_LICENSES) } + // val licenses: Preference by lazy { findPreference(PREF_LICENSES) } val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } val version: Preference by lazy { findPreference(PREF_VERSION) } @@ -54,8 +57,12 @@ class SettingsActivity : BaseActivity() { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.pref_settings) + routing.onClick { + startActivity() + } + donate.onClick { - donate() + startActivity() } // licenses.onClick { @@ -113,9 +120,7 @@ class SettingsActivity : BaseActivity() { startActivity(Intent(Intent.ACTION_VIEW, uri)) } - private fun donate() { - startActivity() - } + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 5066f4d..f7ebf07 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -1,6 +1,7 @@ package com.v2ray.ang.util import android.text.TextUtils +import android.util.Log import com.google.gson.Gson import com.v2ray.ang.AngApplication import com.v2ray.ang.AppConfig @@ -80,6 +81,9 @@ object V2rayConfigUtil { }""") ) } + private val ruleDirectDnsObj: JSONObject by lazy { + JSONObject("""{"type":"field","port":53,"network":"udp","outboundTag":"direct"}""") + } data class Result(var status: Boolean, var content: String) @@ -102,6 +106,7 @@ object V2rayConfigUtil { } else if (config.vmess[config.index].configType == 2) { result = getV2rayConfigType2(app, config) } + Log.d("V2rayConfigUtil", result.content) return result } catch (e: Exception) { e.printStackTrace() @@ -139,13 +144,13 @@ object V2rayConfigUtil { outbound(config, v2rayConfig) //routing - routing(config, v2rayConfig) + routing(config, v2rayConfig, app) //dns customDns(config, v2rayConfig, app) //增加lib2ray - val finalConfig = addLib2ray(v2rayConfig) + val finalConfig = addLib2ray(v2rayConfig, app) result.status = true result.content = finalConfig @@ -292,9 +297,12 @@ object V2rayConfigUtil { /** * routing */ - private fun routing(config: AngConfig, v2rayConfig: V2rayConfig): Boolean { + private fun routing(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { - //绕过大陆网址 + routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig) + routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig) + routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig) + if (config.bypassMainland) { // val rulesItem1 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", "", null, null, "") // rulesItem1.type = "chinasites" @@ -330,6 +338,44 @@ object V2rayConfigUtil { return true } + private fun routingUserRule(userRule: String, tag: String, v2rayConfig: V2rayConfig) { + try { + if (!TextUtils.isEmpty(userRule)) { + //Domain + val rulesDomain = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") + rulesDomain.type = "field" + rulesDomain.outboundTag = tag + rulesDomain.domain = ArrayList() + + //IP + val rulesIP = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") + rulesIP.type = "field" + rulesIP.outboundTag = tag + rulesIP.ip = ArrayList() + + userRule + .split(",") + .forEach { + if (Utils.isIpAddress(it)) { + rulesIP.ip?.add(it) + } else if (Utils.isValidUrl(it)) { + rulesDomain.domain?.add(it) + } + } + if (rulesDomain.domain?.size!! > 0) { + v2rayConfig.routing.settings.rules.add(rulesDomain) + } + if (rulesIP.ip?.size!! > 0) { + v2rayConfig.routing.settings.rules.add(rulesIP) + } + } + + + } catch (e: Exception) { + e.printStackTrace() + } + } + /** * Custom Dns */ @@ -347,11 +393,20 @@ object V2rayConfigUtil { /** * 增加lib2ray */ - private fun addLib2ray(v2rayConfig: V2rayConfig): String { + private fun addLib2ray(v2rayConfig: V2rayConfig, app: AngApplication): String { try { val conf = Gson().toJson(v2rayConfig) val jObj = JSONObject(conf) jObj.put("#lib2ray", lib2rayObj) + + val speedupDomain = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEEDUP_DOMAIN, false) + if (speedupDomain) { + jObj.optJSONObject("routing") + .optJSONObject("settings") + .optJSONArray("rules") + .put(0, ruleDirectDnsObj) + } + return jObj.toString() } catch (e: Exception) { e.printStackTrace() @@ -375,11 +430,13 @@ object V2rayConfigUtil { } } - if (!ret.contains("8.8.8.8")) { - ret.add("8.8.8.8") - } - if (!ret.contains("8.8.4.4")) { - ret.add("8.8.4.4") + if (ret.size <= 0) { + if (!ret.contains("8.8.8.8")) { + ret.add("8.8.8.8") + } + if (!ret.contains("8.8.4.4")) { + ret.add("8.8.4.4") + } } if (!ret.contains("localhost")) { ret.add("localhost") diff --git a/V2rayNG/app/src/main/res/drawable/ic_v.xml b/V2rayNG/app/src/main/res/drawable/ic_v.xml new file mode 100644 index 0000000..24088c0 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_v.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_main.xml b/V2rayNG/app/src/main/res/layout/activity_main.xml index 58ec338..59dbb1f 100644 --- a/V2rayNG/app/src/main/res/layout/activity_main.xml +++ b/V2rayNG/app/src/main/res/layout/activity_main.xml @@ -17,16 +17,24 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - + android:layout_gravity="bottom|end"> + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_routing_settings.xml b/V2rayNG/app/src/main/res/layout/activity_routing_settings.xml new file mode 100644 index 0000000..15d7d80 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_routing_settings.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/fragment_routing_settings.xml b/V2rayNG/app/src/main/res/layout/fragment_routing_settings.xml new file mode 100644 index 0000000..6a56b55 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/fragment_routing_settings.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + diff --git a/V2rayNG/app/src/main/res/menu/menu_routing.xml b/V2rayNG/app/src/main/res/menu/menu_routing.xml new file mode 100644 index 0000000..104f861 --- /dev/null +++ b/V2rayNG/app/src/main/res/menu/menu_routing.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-zh/strings.xml b/V2rayNG/app/src/main/res/values-zh/strings.xml index ea4ec40..dabdb52 100644 --- a/V2rayNG/app/src/main/res/values-zh/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh/strings.xml @@ -70,6 +70,9 @@ 开启Mux多路复用 开启可能会加速,关闭可能会减少断流 + 路由 + 自定义路由 + 捐赠 支持开发者 陆续增加一些试验性的进阶功能 @@ -77,6 +80,9 @@ 远程DNS 远程DNS + 加速域名解析 + 直连解析,可能存在问题,http/tls有效 + 反馈 反馈改进或漏洞至 GitHub @@ -95,8 +101,23 @@ 启动服务 确定 + 路由设置 + 用逗号(,)隔开,可以一行多个,记得保存 + 保存 + 清空 + 扫描并替换 + 扫描并追加 + 二维码 导出至剪贴板 + + + 代理的网址或IP + 直连的网址或IP + 阻止的网址或IP + + + diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index bfec840..e045e67 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -71,6 +71,9 @@ Enable Mux Enable maybe speed up network and switch network maybe flash + Routing + Custom routing + Donate Donate developer Add some experimental advanced features @@ -78,6 +81,9 @@ Remote DNS Remote DNS + Speedup domain name resolution + Valid only for http/tls + Feedback Feedback enhancements or bugs to GitHub @@ -96,8 +102,21 @@ Start Service Confirm + Routing Settings + Separated by commas(,),remember to save + Save + Clear + Scan and replace + Scan and append + QRcode Export to clipboard + + + proxy URL or IP + direct URL or IP + blocked URL or IP + diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 886440e..c9ef638 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -22,9 +22,19 @@ android:summary="@string/summary_pref_remote_dns" android:title="@string/title_pref_remote_dns" /> + + + + - - + + Date: Fri, 5 Jan 2018 17:01:53 +0800 Subject: [PATCH 18/49] add geo function --- .../java/com/v2ray/ang/util/AssetsUtil.java | 35 ++++++ .../kotlin/com/v2ray/ang/dto/AngConfig.kt | 2 - .../com/v2ray/ang/service/V2RayVpnService.kt | 28 +++-- .../com/v2ray/ang/ui/SettingsActivity.kt | 3 +- .../com/v2ray/ang/util/AngConfigManager.kt | 3 - .../main/kotlin/com/v2ray/ang/util/Utils.kt | 35 +++++- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 119 +++++++++--------- .../app/src/main/res/values-zh/strings.xml | 8 +- V2rayNG/app/src/main/res/values/arrays.xml | 7 ++ V2rayNG/app/src/main/res/values/strings.xml | 10 +- .../app/src/main/res/xml/pref_settings.xml | 19 ++- 11 files changed, 172 insertions(+), 97 deletions(-) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/AssetsUtil.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AssetsUtil.java index fe0b736..38f17d9 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/util/AssetsUtil.java +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AssetsUtil.java @@ -1,6 +1,10 @@ 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; @@ -76,6 +80,37 @@ public static String readTextFromAssets(AssetManager assetManager, String fileNa 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; diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt index 0f728ed..752d859 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt @@ -2,8 +2,6 @@ package com.v2ray.ang.dto data class AngConfig( var index: Int, - var bypassMainland: Boolean = false, - var muxEnabled: Boolean = false, var vmess: ArrayList ) { data class VmessBean(var guid: String = "123456", diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 1f20a65..7b43b02 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -10,7 +10,6 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.graphics.Color -//import android.net.ConnectivityManager import android.net.VpnService import android.os.* import android.support.annotation.RequiresApi @@ -18,20 +17,16 @@ import android.support.v4.app.NotificationCompat import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.extension.defaultDPreference -//import com.v2ray.ang.receiver.NetWorkStateReceiver import com.v2ray.ang.ui.MainActivity import com.v2ray.ang.ui.PerAppProxyActivity import com.v2ray.ang.ui.SettingsActivity +import com.v2ray.ang.util.AssetsUtil import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.Utils -import kotlinx.android.synthetic.main.activity_main.* import libv2ray.Libv2ray import libv2ray.V2RayCallbacks import libv2ray.V2RayVPNServiceSupportsSet -import rx.Observable -import rx.android.schedulers.AndroidSchedulers import java.lang.ref.SoftReference -import java.util.concurrent.TimeUnit class V2RayVpnService : VpnService() { companion object { @@ -85,9 +80,10 @@ class V2RayVpnService : VpnService() { builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) - val dnsServers = Utils.getDnsServers() - for (dns in dnsServers) + val dnsServers = Utils.getRemoteDnsServers(defaultDPreference) + for (dns in dnsServers) { builder.addDnsServer(dns) + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) { @@ -171,6 +167,10 @@ class V2RayVpnService : VpnService() { v2rayPoint.configureFile = "V2Ray_internal/ConfigureFileContent" v2rayPoint.configureFileContent = configContent + //next gen tun2ray +// v2rayPoint.upgradeToContext() +// v2rayPoint.optinNextGenerationTunInterface() + v2rayPoint.runLoop() } @@ -207,11 +207,15 @@ class V2RayVpnService : VpnService() { private fun restartV2Ray() { try { + //use custom geo dat +// val path = AssetsUtil.getAssetPath(this, "geoip.dat") +// Libv2ray.setAssetsOverride("geoip.dat", path) +// +// val path2 = AssetsUtil.getAssetPath(this, "geosite.dat") +// Libv2ray.setAssetsOverride("geosite.dat", path2) + stopV2Ray(false) - Observable.timer(1, TimeUnit.SECONDS) - .subscribe { - startV2ray(true) - } + startV2ray(true) } catch (e: Exception) { } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 15264ff..bb46d21 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -20,13 +20,14 @@ import org.jetbrains.anko.startActivity class SettingsActivity : BaseActivity() { companion object { - const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland" + // const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland" // const val PREF_START_ON_BOOT = "pref_start_on_boot" const val PREF_PER_APP_PROXY = "pref_per_app_proxy" const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_REMOTE_DNS = "pref_remote_dns" const val PREF_SPEEDUP_DOMAIN = "pref_speedup_domain" + const val PREF_ROUTING_MODE = "pref_routing_mode" const val PREF_ROUTING = "pref_routing" const val PREF_DONATE = "pref_donate" // const val PREF_LICENSES = "pref_licenses" diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 8805484..3c5638a 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -134,9 +134,6 @@ object AngConfigManager { */ fun genStoreV2rayConfig(): Boolean { try { - angConfig.bypassMainland = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_BYPASS_MAINLAND, false) - angConfig.muxEnabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) - val result = V2rayConfigUtil.getV2rayConfig(app, angConfig) if (result.status) { app.defaultDPreference.setPrefString(PREF_CURR_CONFIG, result.content) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 3e16597..96461b1 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -13,10 +13,13 @@ import java.util.* import kotlin.collections.HashMap import android.app.ActivityManager import android.content.ClipData +import android.text.TextUtils import android.util.Patterns import android.webkit.URLUtil import com.v2ray.ang.AppConfig import com.v2ray.ang.service.V2RayVpnService +import com.v2ray.ang.ui.SettingsActivity +import me.dozen.dpreference.DPreference object Utils { @@ -105,13 +108,33 @@ object Utils { } /** - * get dns servers + * get remote dns servers from preference */ - fun getDnsServers(): Array { - val ret = LinkedHashSet() - ret.add("8.8.8.8") - ret.add("8.8.4.4") - return ret.toTypedArray() + fun getRemoteDnsServers(defaultDPreference: DPreference): List { + val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, "") + val ret = ArrayList() + if (!TextUtils.isEmpty(remoteDns)) { + remoteDns + .split(",") + .forEach { + if (Utils.isIpAddress(it)) { + ret.add(it) + } + } + } + + if (ret.size <= 0) { + if (!ret.contains("8.8.8.8")) { + ret.add("8.8.8.8") + } + if (!ret.contains("8.8.4.4")) { + ret.add("8.8.4.4") + } + } +// if (!ret.contains("localhost")) { +// ret.add("localhost") +// } + return ret } /** diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index f7ebf07..5e18bd3 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -141,7 +141,7 @@ object V2rayConfigUtil { // } //vmess协议服务器配置 - outbound(config, v2rayConfig) + outbound(config, v2rayConfig, app) //routing routing(config, v2rayConfig, app) @@ -195,7 +195,7 @@ object V2rayConfigUtil { /** * vmess协议服务器配置 */ - private fun outbound(config: AngConfig, v2rayConfig: V2rayConfig): Boolean { + private fun outbound(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { val vmess = config.vmess[config.index] v2rayConfig.outbound.settings.vnext[0].address = vmess.address @@ -206,7 +206,8 @@ object V2rayConfigUtil { v2rayConfig.outbound.settings.vnext[0].users[0].security = vmess.security //Mux - v2rayConfig.outbound.mux.enabled = config.muxEnabled + val muxEnabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) + v2rayConfig.outbound.mux.enabled = muxEnabled //远程服务器底层传输配置 v2rayConfig.outbound.streamSettings = boundStreamSettings(config) @@ -303,33 +304,31 @@ object V2rayConfigUtil { routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig) routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig) - if (config.bypassMainland) { -// val rulesItem1 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", "", null, null, "") -// rulesItem1.type = "chinasites" -// rulesItem1.outboundTag = "direct" -// v2rayConfig.routing.settings.rules.add(rulesItem1) -// -// val rulesItem2 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", "", null, null, "") -// rulesItem2.type = "chinaip" -// rulesItem2.outboundTag = "direct" -// v2rayConfig.routing.settings.rules.add(rulesItem2) - -// v2rayConfig.routing.settings.rules[0].domain?.add("geosite:cn") -// v2rayConfig.routing.settings.rules[0].ip?.add("geoip:cn") - - val rulesItem1 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") - rulesItem1.type = "field" - rulesItem1.outboundTag = "direct" - rulesItem1.domain = ArrayList() - rulesItem1.domain?.add("geosite:cn") - v2rayConfig.routing.settings.rules.add(rulesItem1) - - val rulesItem2 = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") - rulesItem2.type = "field" - rulesItem2.outboundTag = "direct" - rulesItem2.ip = ArrayList() - rulesItem2.ip?.add("geoip:cn") - v2rayConfig.routing.settings.rules.add(rulesItem2) + val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0") + when (routingMode) { + "0" -> { + } + "1" -> { + routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) + } + "2" -> { + routingGeo("", "custom:reject", AppConfig.TAG_BLOCKED, v2rayConfig) + routingGeo("", "surge:reject", AppConfig.TAG_BLOCKED, v2rayConfig) + + routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) + + routingGeo("", "custom:proxy", AppConfig.TAG_AGENT, v2rayConfig) + routingGeo("", "surge:proxy", AppConfig.TAG_AGENT, v2rayConfig) + routingGeo("domain", "gfwlist:proxy", AppConfig.TAG_AGENT, v2rayConfig) + } } } catch (e: Exception) { e.printStackTrace() @@ -338,6 +337,34 @@ object V2rayConfigUtil { return true } + private fun routingGeo(ipOrDomain: String, code: String, tag: String, v2rayConfig: V2rayConfig) { + try { + if (!TextUtils.isEmpty(code)) { + //IP + if (ipOrDomain == "ip" || ipOrDomain == "") { + val rulesIP = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") + rulesIP.type = "field" + rulesIP.outboundTag = tag + rulesIP.ip = ArrayList() + rulesIP.ip?.add("geoip:$code") + v2rayConfig.routing.settings.rules.add(rulesIP) + } + + if (ipOrDomain == "domain" || ipOrDomain == "") { + //Domain + val rulesDomain = V2rayConfig.RoutingBean.SettingsBean.RulesBean("", null, null, "") + rulesDomain.type = "field" + rulesDomain.outboundTag = tag + rulesDomain.domain = ArrayList() + rulesDomain.domain?.add("geosite:$code") + v2rayConfig.routing.settings.rules.add(rulesDomain) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + private fun routingUserRule(userRule: String, tag: String, v2rayConfig: V2rayConfig) { try { if (!TextUtils.isEmpty(userRule)) { @@ -381,7 +408,7 @@ object V2rayConfigUtil { */ private fun customDns(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { - v2rayConfig.dns.servers = getRemoteDnsServers(app) + v2rayConfig.dns.servers = Utils.getRemoteDnsServers(app.defaultDPreference) } catch (e: Exception) { e.printStackTrace() return false @@ -414,36 +441,6 @@ object V2rayConfigUtil { } } - /** - * get remote dns servers from preference - */ - private fun getRemoteDnsServers(app: AngApplication): List { - val ret = ArrayList() - val remoteDns = app.defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, "") - if (!TextUtils.isEmpty(remoteDns)) { - remoteDns - .split(",") - .forEach { - if (Utils.isIpAddress(it)) { - ret.add(it) - } - } - } - - if (ret.size <= 0) { - if (!ret.contains("8.8.8.8")) { - ret.add("8.8.8.8") - } - if (!ret.contains("8.8.4.4")) { - ret.add("8.8.4.4") - } - } - if (!ret.contains("localhost")) { - ret.add("localhost") - } - return ret - } - /** * 增加lib2ray */ diff --git a/V2rayNG/app/src/main/res/values-zh/strings.xml b/V2rayNG/app/src/main/res/values-zh/strings.xml index dabdb52..cbedaf2 100644 --- a/V2rayNG/app/src/main/res/values-zh/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh/strings.xml @@ -61,9 +61,6 @@ 关于 进阶设置 - 绕过大陆地址 - 绕过大陆地址 - 分应用代理 分应用代理仅支持 Android 5.0 Lollipop 及更高 @@ -119,5 +116,10 @@ 阻止的网址或IP + + 全局 + 绕过大陆地址 + Geo全家桶 + diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 337421f..35835eb 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -37,4 +37,11 @@ tls + + + 0 + 1 + 2 + + diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index e045e67..58ce889 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -62,9 +62,6 @@ About Advanced Settings - Bypass mainland - Bypass mainland - Per-app proxy Per-app proxy mode only support Android 5.0 Lollipop or higher @@ -119,4 +116,11 @@ direct URL or IP blocked URL or IP + + + Global + Bypass mainland + Geo Family + + diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index c9ef638..989e2af 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -1,11 +1,10 @@ - - + + + + + + + android:title="@string/summary_pref_routing" /> Date: Tue, 17 Apr 2018 16:33:27 +0800 Subject: [PATCH 19/49] zh-TW --- .../strings.xml | 0 .../{values-zh => values-zh-rCN}/strings.xml | 0 .../main/res/values-zh-rTW-v21/strings.xml | 4 + .../src/main/res/values-zh-rTW/strings.xml | 126 ++++++++++++++++++ 4 files changed, 130 insertions(+) rename V2rayNG/app/src/main/res/{values-zh-v21 => values-zh-rCN-v21}/strings.xml (100%) rename V2rayNG/app/src/main/res/{values-zh => values-zh-rCN}/strings.xml (100%) create mode 100644 V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml create mode 100644 V2rayNG/app/src/main/res/values-zh-rTW/strings.xml diff --git a/V2rayNG/app/src/main/res/values-zh-v21/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN-v21/strings.xml similarity index 100% rename from V2rayNG/app/src/main/res/values-zh-v21/strings.xml rename to V2rayNG/app/src/main/res/values-zh-rCN-v21/strings.xml diff --git a/V2rayNG/app/src/main/res/values-zh/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml similarity index 100% rename from V2rayNG/app/src/main/res/values-zh/strings.xml rename to V2rayNG/app/src/main/res/values-zh-rCN/strings.xml diff --git a/V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml new file mode 100644 index 0000000..5c40f5f --- /dev/null +++ b/V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml @@ -0,0 +1,4 @@ + + + 為選擇的應用程式設定 Proxy + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..1cc7e19 --- /dev/null +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,126 @@ + + + v2rayNG + 切換 + 切換 + 首次使用此功能,請使用此應用程式來啟用 VPN + + + 停止 + 無法取得此權限 + 按一下瞭解更多 + 啟動服務 + 停止服務 + 啟動服務成功 + 啟動服務失敗 + + + 伺服器 + 新增組態 + 儲存組態 + 刪除組態 + 自 QR 碼匯入組態 + 自剪貼簿匯入組態 + 手動鍵入 + 自本機匯入自訂組態 + 自網址匯入自訂組態 + 掃描網址匯入自訂組態 + 確定刪除? + 備註 + 位址 + + 使用者識別碼 + AlterId + 安全性 + 網路 + 更多功能 + 標頭類型 + 要求 主機/ws 路徑/ws(路徑;主機) + 傳輸層安全性 + 成功 + 失敗 + 無資料 + 通訊協定不正確 + 解碼失敗 + 選取一個設定檔 + 請安裝檔案總管。 + 自訂組態 + 無效組態 + 內容 + 剪貼簿內無資料 + 網址無效 + + + 載入 + 全選 + 輸入關鍵字 + 略過模式 + + + + 設定 + 關於 + 進階設定 + + Proxy 個別應用程式 + Proxy 個別應用程式模式只支援 Android 5.0 (Lollipop) 或更高 + + 啟用 Mux + 啟用或許會加快網路速度,切換或許會閃爍 + + 路由 + 自訂路由 + + 捐款 + 向開發人員捐款 + 新增一些實驗性進階功能 + + 遠端 DNS + 遠端 DNS + + 加快網域名稱解析速度 + 只對 HTTP/TLS 有效 + + 回饋 + 回饋提升或前往 GitHub 回報 Bug + + 版本 + + 錯誤設定: + Error querying inventory + 購買錯誤: + 購買錯誤,真實性驗證失敗。 + 感謝您的捐款! + 捐款 + + Logcat + 複製 + + 啟動服務 + 確定 + + 路由設定 + 以英文逗號「,」分隔,記得儲存唷 + 儲存 + 清除 + 掃描並取代 + 掃描並附加 + + + QR 碼 + 匯出至剪貼簿 + + + + Proxy 網址或 IP + 直連網址或 IP + 已封鎖的網址或 IP + + + + 全球 + 略過中國大陸 + Geo 家族 + + + From 93006603ac91e11ea80f1cbd4258e3ce9453c247 Mon Sep 17 00:00:00 2001 From: LNDDYL Date: Tue, 17 Apr 2018 17:00:43 +0800 Subject: [PATCH 20/49] fix --- V2rayNG/app/src/main/res/values-zh-rTW/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 1cc7e19..a3569da 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -1,4 +1,4 @@ - + v2rayNG 切換 From b60d562a44c4173452c8e3c230fb57c0a55a5f28 Mon Sep 17 00:00:00 2001 From: LNDDYL Date: Tue, 17 Apr 2018 17:01:22 +0800 Subject: [PATCH 21/49] fix --- V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml index 5c40f5f..81da2b4 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW-v21/strings.xml @@ -1,4 +1,4 @@ - + 為選擇的應用程式設定 Proxy - \ No newline at end of file + From d4620524a22c78d6bb443b38a9e3ec2c4c707c09 Mon Sep 17 00:00:00 2001 From: 2dust Date: Thu, 26 Apr 2018 11:05:41 +0800 Subject: [PATCH 22/49] issue --- .github/issue_template.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/issue_template.md b/.github/issue_template.md index be1d9bb..1251dde 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,4 +1,5 @@ -在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。填写完问题模板后请删除提示,否则可能会被直接关闭问题。 +在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。 + ### 预期行为 描述你认为应该发生什么 From e0f47c5a80d336c7a710e7ee04dec00dacad7802 Mon Sep 17 00:00:00 2001 From: 2dust Date: Wed, 2 May 2018 10:22:31 +0800 Subject: [PATCH 23/49] support h2 --- .../ang/helper/ItemTouchHelperAdapter.java | 58 +++++++++ .../ang/helper/ItemTouchHelperViewHolder.java | 41 ++++++ .../v2ray/ang/helper/OnStartDragListener.java | 33 +++++ .../helper/SimpleItemTouchHelperCallback.java | 123 ++++++++++++++++++ .../kotlin/com/v2ray/ang/AngApplication.kt | 4 +- .../kotlin/com/v2ray/ang/dto/AngConfig.kt | 4 +- .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 12 +- .../kotlin/com/v2ray/ang/dto/VmessQRCode.kt | 4 +- .../com/v2ray/ang/receiver/TaskerReceiver.kt | 3 +- .../com/v2ray/ang/receiver/WidgetProvider.kt | 4 +- .../com/v2ray/ang/service/V2RayVpnService.kt | 11 +- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 24 +++- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 31 ++++- .../v2ray/ang/ui/RoutingSettingsFragment.kt | 2 +- .../kotlin/com/v2ray/ang/ui/ServerActivity.kt | 3 + .../com/v2ray/ang/ui/SettingsActivity.kt | 12 +- .../kotlin/com/v2ray/ang/ui/TaskerActivity.kt | 19 ++- .../com/v2ray/ang/util/AngConfigManager.kt | 85 +++++++++++- .../main/kotlin/com/v2ray/ang/util/Utils.kt | 11 ++ .../com/v2ray/ang/util/V2rayConfigUtil.kt | 62 ++++++--- .../src/main/res/layout/activity_server.xml | 19 +++ .../src/main/res/values-zh-rCN/strings.xml | 4 +- .../src/main/res/values-zh-rTW/strings.xml | 4 +- V2rayNG/app/src/main/res/values/arrays.xml | 1 + V2rayNG/app/src/main/res/values/strings.xml | 4 +- .../app/src/main/res/xml/pref_settings.xml | 8 +- 26 files changed, 526 insertions(+), 60 deletions(-) create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperAdapter.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperViewHolder.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/helper/OnStartDragListener.java create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperAdapter.java b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperAdapter.java new file mode 100644 index 0000000..4cf695f --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperAdapter.java @@ -0,0 +1,58 @@ +/* + * 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; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. + * + * @author Paul Burke (ipaulpro) + */ +public interface ItemTouchHelperAdapter { + + /** + * Called when an item has been dragged far enough to trigger a move. This is called every time + * an item is shifted, and not at the end of a "drop" event.
+ *
+ * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after + * adjusting the underlying data to reflect this move. + * + * @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); + + + /** + * Called when an item has been dismissed by a swipe.
+ *
+ * Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after + * 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() + */ + void onItemDismiss(int position); +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperViewHolder.java b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperViewHolder.java new file mode 100644 index 0000000..e20014d --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/ItemTouchHelperViewHolder.java @@ -0,0 +1,41 @@ +/* + * 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.helper.ItemTouchHelper; + +/** + * Interface to notify an item ViewHolder of relevant callbacks from {@link + * ItemTouchHelper.Callback}. + * + * @author Paul Burke (ipaulpro) + */ +public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped. + * Implementations should update the item view to indicate it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item + * state should be cleared. + */ + void onItemClear(); +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/OnStartDragListener.java b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/OnStartDragListener.java new file mode 100644 index 0000000..163f94d --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/OnStartDragListener.java @@ -0,0 +1,33 @@ +/* + * 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); + +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java new file mode 100644 index 0000000..2d281d5 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java @@ -0,0 +1,123 @@ +/* + * 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.graphics.Canvas; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and + * swipe-to-dismiss. Drag events are automatically started by an item long-press.
+ *
+ * Expects the RecyclerView.Adapter to listen for {@link + * ItemTouchHelperAdapter} callbacks and the RecyclerView.ViewHolder to implement + * {@link ItemTouchHelperViewHolder}. + * + * @author Paul Burke (ipaulpro) + */ +public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback { + + public static final float ALPHA_FULL = 1.0f; + + private final ItemTouchHelperAdapter mAdapter; + + public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { + mAdapter = adapter; + } + + @Override + public boolean isLongPressDragEnabled() { + return true; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, 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; + final int swipeFlags = 0; + return makeMovementFlags(dragFlags, swipeFlags); + } else { + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; + return makeMovementFlags(dragFlags, swipeFlags); + } + } + + @Override + public boolean onMove(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()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { + // Notify the adapter of the dismissal + mAdapter.onItemDismiss(viewHolder.getAdapterPosition()); + } + + @Override + public void onChildDraw(Canvas c, RecyclerView recyclerView, 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(); + viewHolder.itemView.setAlpha(alpha); + viewHolder.itemView.setTranslationX(dX); + } else { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + } + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + // We only want the active item to change + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + if (viewHolder instanceof ItemTouchHelperViewHolder) { + // Let the view holder know that this item is being moved or dragged + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } + } + + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + viewHolder.itemView.setAlpha(ALPHA_FULL); + + if (viewHolder instanceof ItemTouchHelperViewHolder) { + // Tell the view holder it's time to restore the idle state + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); + } + } +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt index ee5074c..e54812c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt @@ -1,7 +1,7 @@ package com.v2ray.ang import android.app.Application -import com.squareup.leakcanary.LeakCanary +//import com.squareup.leakcanary.LeakCanary import com.v2ray.ang.util.AngConfigManager import me.dozen.dpreference.DPreference import org.jetbrains.anko.defaultSharedPreferences @@ -19,7 +19,7 @@ class AngApplication : Application() { override fun onCreate() { super.onCreate() - LeakCanary.install(this) +// LeakCanary.install(this) firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE if (firstRun) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt index 752d859..202b568 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt @@ -14,6 +14,8 @@ data class AngConfig( var remarks: String = "def", var headerType: String = "", var requestHost: String = "", + var path: String = "", var streamSecurity: String = "", - var configType: Int = 1) + var configType: Int = 1, + var configVersion: Int = 1) } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt index 93372b2..ebfb8fb 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt @@ -44,7 +44,10 @@ data class V2rayConfig(val port: Int, var security: String, var tcpSettings: TcpsettingsBean?, var kcpsettings: KcpsettingsBean?, - var wssettings: WssettingsBean?) { + var wssettings: WssettingsBean?, + var httpsettings: HttpsettingsBean?, + var tlssettings: TlssettingsBean? + ) { data class TcpsettingsBean(var connectionReuse: Boolean = true, var header: HeaderBean = HeaderBean()) { @@ -69,6 +72,11 @@ data class V2rayConfig(val port: Int, var headers: HeadersBean = HeadersBean()) { data class HeadersBean(var Host: String = "") } + + data class HttpsettingsBean(var host: List = ArrayList(), var path: String = "") + + data class TlssettingsBean(var allowInsecure: Boolean = true, + var serverName: String = "") } } @@ -92,7 +100,7 @@ data class V2rayConfig(val port: Int, var rules: ArrayList) { data class RulesBean(var type: String, - //var port: String, + //var port: String, var ip: ArrayList?, var domain: ArrayList?, var outboundTag: String) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VmessQRCode.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VmessQRCode.kt index 1b0a186..3061801 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VmessQRCode.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VmessQRCode.kt @@ -1,6 +1,7 @@ package com.v2ray.ang.dto -data class VmessQRCode(var ps: String = "", +data class VmessQRCode(var v: String = "", + var ps: String = "", var add: String = "", var port: String = "", var id: String = "", @@ -8,4 +9,5 @@ data class VmessQRCode(var ps: String = "", var net: String = "", var type: String = "", var host: String = "", + var path: String = "", var tls: String = "") \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt index 5baab3b..83d0929 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/TaskerReceiver.kt @@ -3,6 +3,7 @@ package com.v2ray.ang.receiver 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 @@ -16,7 +17,7 @@ class TaskerReceiver : BroadcastReceiver() { val switch = bundle?.getBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, false) val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, "") - if (switch == null || guid == null) { + if (switch == null || guid == null || TextUtils.isEmpty(guid)) { return } else if (switch) { Utils.startVService(context, guid) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt index de56ce8..eddc214 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/receiver/WidgetProvider.kt @@ -38,10 +38,10 @@ class WidgetProvider : AppWidgetProvider() { val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService") if (isRunning) { - context.toast(R.string.toast_services_stop) +// context.toast(R.string.toast_services_stop) Utils.stopVService(context) } else { - context.toast(R.string.toast_services_start) +// context.toast(R.string.toast_services_start) Utils.startVService(context) } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 7b43b02..1bcd663 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -36,7 +36,11 @@ class V2RayVpnService : VpnService() { fun startV2Ray(context: Context) { val intent = Intent(context.applicationContext, V2RayVpnService::class.java) - context.startService(intent) + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { + context.startForegroundService(intent) + } else { + context.startService(intent) + } } } @@ -130,9 +134,10 @@ class V2RayVpnService : VpnService() { v2rayPoint.vpnSupportReady() if (v2rayPoint.isRunning) { MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "") - showNotification() +// showNotification() } else { MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "") + cancelNotification() } } @@ -174,7 +179,7 @@ class V2RayVpnService : VpnService() { v2rayPoint.runLoop() } - // showNotification() + showNotification() } private fun stopV2Ray(isForced: Boolean = true) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 88f15f6..86060d6 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -22,9 +22,12 @@ import org.jetbrains.anko.* import java.lang.ref.SoftReference import java.net.URL import android.content.IntentFilter +import android.support.v7.widget.helper.ItemTouchHelper import rx.Observable import rx.android.schedulers.AndroidSchedulers import java.util.concurrent.TimeUnit +import com.v2ray.ang.helper.SimpleItemTouchHelperCallback + class MainActivity : BaseActivity() { companion object { @@ -47,6 +50,7 @@ class MainActivity : BaseActivity() { } private val adapter by lazy { MainRecyclerAdapter(this) } + private var mItemTouchHelper: ItemTouchHelper? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -65,14 +69,18 @@ class MainActivity : BaseActivity() { } } + recycler_view.setHasFixedSize(true) recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.adapter = adapter + + val callback = SimpleItemTouchHelperCallback(adapter) + mItemTouchHelper = ItemTouchHelper(callback) + mItemTouchHelper?.attachToRecyclerView(recycler_view) } fun startV2Ray() { - fabProgressCircle?.show() - - toast(R.string.toast_services_start) + showCircle() +// toast(R.string.toast_services_start) Utils.startVService(this) } @@ -181,7 +189,7 @@ class MainActivity : BaseActivity() { .addCategory(Intent.CATEGORY_DEFAULT) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) } catch (e: Exception) { - RxPermissions.getInstance(this) + RxPermissions(this) .request(Manifest.permission.CAMERA) .subscribe { if (it) @@ -290,7 +298,7 @@ class MainActivity : BaseActivity() { * read content from uri */ private fun readContentFromUri(uri: Uri) { - RxPermissions.getInstance(this) + RxPermissions(this) .request(Manifest.permission.READ_EXTERNAL_STORAGE) .subscribe { if (it) { @@ -371,9 +379,13 @@ class MainActivity : BaseActivity() { return super.onKeyDown(keyCode, event) } + fun showCircle() { + fabProgressCircle?.show() + } + fun hideCircle() { try { - Observable.timer(1, TimeUnit.SECONDS) + Observable.timer(500, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { if (fabProgressCircle.isShown) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 9451921..aabe391 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -6,19 +6,24 @@ import android.view.View import android.view.ViewGroup import com.v2ray.ang.R import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.helper.ItemTouchHelperAdapter +import com.v2ray.ang.helper.ItemTouchHelperViewHolder import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.Utils +import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.item_qrcode.view.* import kotlinx.android.synthetic.main.item_recycler_main.view.* import org.jetbrains.anko.* +import java.util.* -class MainRecyclerAdapter(val activity: BaseActivity) : RecyclerView.Adapter() { +class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter() + , ItemTouchHelperAdapter { companion object { private const val VIEW_TYPE_ITEM = 1 private const val VIEW_TYPE_FOOTER = 2 } - private var mActivity: BaseActivity = activity + private var mActivity: MainActivity = activity private lateinit var configs: AngConfig private val share_method: Array by lazy { mActivity.resources.getStringArray(R.array.share_method) @@ -103,7 +108,9 @@ class MainRecyclerAdapter(val activity: BaseActivity) : RecyclerView.Adapter= 0) { @@ -102,6 +103,7 @@ class ServerActivity : BaseActivity() { sp_header_type.setSelection(0) et_request_host.text = null + et_path.text = null sp_stream_security.setSelection(0) return true } @@ -123,6 +125,7 @@ class ServerActivity : BaseActivity() { vmess.headerType = headertypes[sp_header_type.selectedItemPosition] vmess.requestHost = et_request_host.text.toString() + vmess.path = et_path.text.toString() vmess.streamSecurity = streamsecuritys[sp_stream_security.selectedItemPosition] if (TextUtils.isEmpty(vmess.remarks)) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index bb46d21..38416d3 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -13,6 +13,7 @@ import com.v2ray.ang.InappBuyActivity import com.v2ray.ang.R import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.onClick +import com.v2ray.ang.util.Utils import libv2ray.Libv2ray import org.jetbrains.anko.act import org.jetbrains.anko.defaultSharedPreferences @@ -25,7 +26,7 @@ class SettingsActivity : BaseActivity() { const val PREF_PER_APP_PROXY = "pref_per_app_proxy" const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_REMOTE_DNS = "pref_remote_dns" - const val PREF_SPEEDUP_DOMAIN = "pref_speedup_domain" +// const val PREF_SPEEDUP_DOMAIN = "pref_speedup_domain" const val PREF_ROUTING_MODE = "pref_routing_mode" const val PREF_ROUTING = "pref_routing" @@ -75,7 +76,7 @@ class SettingsActivity : BaseActivity() { // } feedback.onClick { - openUri("https://github.com/2dust/v2rayNG/issues") + Utils.openUri(activity, "https://github.com/2dust/v2rayNG/issues") } perAppProxy.setOnPreferenceClickListener { @@ -115,13 +116,6 @@ class SettingsActivity : BaseActivity() { act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false)) } } - - private fun openUri(uriString: String) { - val uri = Uri.parse(uriString) - startActivity(Intent(Intent.ACTION_VIEW, uri)) - } - - } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt index 8d1bdad..36ddce9 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/TaskerActivity.kt @@ -9,6 +9,7 @@ import java.util.ArrayList import com.v2ray.ang.R import com.v2ray.ang.util.AngConfigManager import android.content.Intent +import android.text.TextUtils import android.view.Menu import android.view.MenuItem import com.google.zxing.WriterException @@ -19,6 +20,7 @@ import kotlinx.android.synthetic.main.activity_tasker.* class TaskerActivity : BaseActivity() { private var listview: ListView? = null private var lstData: ArrayList = ArrayList() + private var lstGuid: ArrayList = ArrayList() private val vmess = AngConfigManager.configs.vmess override fun onCreate(savedInstanceState: Bundle?) { @@ -27,6 +29,7 @@ class TaskerActivity : BaseActivity() { vmess.forEach { lstData.add(it.remarks) + lstGuid.add(it.guid) } val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_single_choice, lstData) @@ -42,10 +45,14 @@ class TaskerActivity : BaseActivity() { val switch = bundle?.getBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, false) val guid = bundle?.getString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, "") - if (switch == null || guid == null) { + if (switch == null || TextUtils.isEmpty(guid)) { return } else { switch_start_service.isChecked = switch + val pos = lstGuid.indexOf(guid.toString()) + if (pos >= 0) { + listview?.setItemChecked(pos, true) + } } } catch (e: WriterException) { e.printStackTrace() @@ -64,15 +71,17 @@ class TaskerActivity : BaseActivity() { extraBundle.putString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, vmess[position].guid) val intent = Intent() - var remarks = vmess[position].remarks + val remarks = vmess[position].remarks + var blurb = "" + if (switch_start_service.isChecked) { - remarks = "Start $remarks" + blurb = "Start $remarks" } else { - remarks = "Stop $remarks" + blurb = "Stop $remarks" } intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle) - intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, remarks) + intent.putExtra(AppConfig.TASKER_EXTRA_STRING_BLURB, blurb) setResult(Activity.RESULT_OK, intent) finish() } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 3c5638a..21dfeb8 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -12,7 +12,7 @@ import com.v2ray.ang.AppConfig.VMESS_PROTOCOL import com.v2ray.ang.R import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.VmessQRCode -import com.v2ray.ang.ui.SettingsActivity +import java.util.* object AngConfigManager { private lateinit var app: AngApplication @@ -37,6 +37,11 @@ object AngConfigManager { } else { angConfig = AngConfig(0, vmess = arrayListOf(AngConfig.VmessBean())) } + + for (i in angConfig.vmess.indices) { + upgradeServerVersion(angConfig.vmess[i]) + } + } catch (e: Exception) { e.printStackTrace() } @@ -47,6 +52,8 @@ object AngConfigManager { */ fun addServer(vmess: AngConfig.VmessBean, index: Int): Int { try { + vmess.configVersion = 2 + if (index >= 0) { //edit angConfig.vmess[index] = vmess @@ -99,6 +106,24 @@ object AngConfigManager { return 0 } + fun swapServer(fromPosition: Int, toPosition: Int): Int { + try { + Collections.swap(angConfig.vmess, fromPosition, toPosition) + + val index = angConfig.index + if (index == fromPosition) { + angConfig.index = toPosition + } else if (index == toPosition) { + angConfig.index = fromPosition + } + storeConfigFile() + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } + /** * set active server */ @@ -198,6 +223,7 @@ object AngConfigManager { vmess.network = "tcp" vmess.headerType = "none" + vmess.configVersion = Utils.parseInt(vmessQRCode.v) vmess.remarks = vmessQRCode.ps vmess.address = vmessQRCode.add vmess.port = Utils.parseInt(vmessQRCode.port) @@ -206,8 +232,11 @@ object AngConfigManager { vmess.network = vmessQRCode.net vmess.headerType = vmessQRCode.type vmess.requestHost = vmessQRCode.host + vmess.path = vmessQRCode.path vmess.streamSecurity = vmessQRCode.tls + upgradeServerVersion(vmess) + addServer(vmess, -1) } catch (e: Exception) { e.printStackTrace() @@ -226,6 +255,7 @@ object AngConfigManager { } val vmess = angConfig.vmess[index] val vmessQRCode = VmessQRCode() + vmessQRCode.v = vmess.configVersion.toString() vmessQRCode.ps = vmess.remarks vmessQRCode.add = vmess.address vmessQRCode.port = vmess.port.toString() @@ -234,6 +264,7 @@ object AngConfigManager { vmessQRCode.net = vmess.network vmessQRCode.type = vmess.headerType vmessQRCode.host = vmess.requestHost + vmessQRCode.path = vmess.path vmessQRCode.tls = vmess.streamSecurity val json = Gson().toJson(vmessQRCode) val conf = VMESS_PROTOCOL + Utils.encode(json) @@ -296,6 +327,7 @@ object AngConfigManager { //add val vmess = AngConfig.VmessBean() + vmess.configVersion = 2 vmess.configType = 2 vmess.guid = guid vmess.remarks = vmess.guid @@ -344,4 +376,55 @@ object AngConfigManager { } return 0 } + + /** + * upgrade + */ + fun upgradeServerVersion(vmess: AngConfig.VmessBean): Int { + try { + if (vmess.configVersion == 2) { + return 0 + } + + when (vmess.network) { + "kcp" -> { + } + "ws" -> { + var path = "" + var host = "" + val lstParameter = vmess.requestHost.split(";") + if (lstParameter.size > 0) { + path = lstParameter.get(0).trim() + } + if (lstParameter.size > 1) { + path = lstParameter.get(0).trim() + host = lstParameter.get(1).trim() + } + vmess.path = path + vmess.requestHost = host + } + "h2" -> { + var path = "" + var host = "" + val lstParameter = vmess.requestHost.split(";") + if (lstParameter.size > 0) { + path = lstParameter.get(0).trim() + } + if (lstParameter.size > 1) { + path = lstParameter.get(0).trim() + host = lstParameter.get(1).trim() + } + vmess.path = path + vmess.requestHost = host + } + else -> { + } + } + vmess.configVersion = 2 + return 0 + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 96461b1..57f6a8f 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -13,13 +13,17 @@ import java.util.* import kotlin.collections.HashMap import android.app.ActivityManager import android.content.ClipData +import android.content.Intent +import android.net.Uri import android.text.TextUtils import android.util.Patterns import android.webkit.URLUtil import com.v2ray.ang.AppConfig +import com.v2ray.ang.R import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.ui.SettingsActivity import me.dozen.dpreference.DPreference +import org.jetbrains.anko.toast object Utils { @@ -249,6 +253,7 @@ object Utils { * startVService */ fun startVService(context: Context): Boolean { + context.toast(R.string.toast_services_start) if (AngConfigManager.genStoreV2rayConfig()) { V2RayVpnService.startV2Ray(context) return true @@ -277,8 +282,14 @@ object Utils { * stopVService */ fun stopVService(context: Context) { + context.toast(R.string.toast_services_stop) MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "") } + + fun openUri(context: Context, uriString: String) { + val uri = Uri.parse(uriString) + context.startActivity(Intent(Intent.ACTION_VIEW, uri)) + } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 5e18bd3..12fd506 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -12,6 +12,7 @@ import com.v2ray.ang.ui.SettingsActivity import org.json.JSONArray import org.json.JSONException import org.json.JSONObject +import java.util.logging.Logger object V2rayConfigUtil { private val lib2rayObj: JSONObject by lazy { @@ -152,6 +153,8 @@ object V2rayConfigUtil { //增加lib2ray val finalConfig = addLib2ray(v2rayConfig, app) + Log.d("config", finalConfig) + result.status = true result.content = finalConfig return result @@ -214,9 +217,15 @@ object V2rayConfigUtil { //如果非ip if (!Utils.isIpAddress(vmess.address)) { - lib2rayObj.optJSONObject("preparedDomainName") + val addr = String.format("%s:%s", vmess.address, vmess.port) + val domainName = lib2rayObj.optJSONObject("preparedDomainName") .optJSONArray("domainName") - .put(String.format("%s:%s", vmess.address, vmess.port)) + if (domainName.length() > 0) { + for (index in 0 until domainName.length()) { + domainName.remove(index) + } + } + domainName.put(addr) } } catch (e: Exception) { e.printStackTrace() @@ -229,7 +238,7 @@ object V2rayConfigUtil { * 远程服务器底层传输配置 */ private fun boundStreamSettings(config: AngConfig): V2rayConfig.OutboundBean.StreamSettingsBean { - val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null) + val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null, null, null) try { //远程服务器底层传输配置 streamSettings.network = config.vmess[config.index].network @@ -253,15 +262,36 @@ object V2rayConfigUtil { "ws" -> { val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean() wssettings.connectionReuse = true - val lstParameter = config.vmess[config.index].requestHost.split(";") - if (lstParameter.size > 0) { - wssettings.path = lstParameter.get(0) - } - if (lstParameter.size > 1) { + val host = config.vmess[config.index].requestHost.trim() + val path = config.vmess[config.index].path.trim() + + if (!TextUtils.isEmpty(host)) { wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean.HeadersBean() - wssettings.headers.Host = lstParameter.get(1) + wssettings.headers.Host = host + } + if (!TextUtils.isEmpty(path)) { + wssettings.path = path } streamSettings.wssettings = wssettings + + val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean() + tlssettings.allowInsecure = true + streamSettings.tlssettings = tlssettings + } + "h2" -> { + val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpsettingsBean() + val host = config.vmess[config.index].requestHost.trim() + val path = config.vmess[config.index].path.trim() + + if (!TextUtils.isEmpty(host)) { + httpsettings.host = host.split(",").map { it.trim() } + } + httpsettings.path = path + streamSettings.httpsettings = httpsettings + + val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean() + tlssettings.allowInsecure = true + streamSettings.tlssettings = tlssettings } else -> { //tcp带http伪装 @@ -426,13 +456,13 @@ object V2rayConfigUtil { val jObj = JSONObject(conf) jObj.put("#lib2ray", lib2rayObj) - val speedupDomain = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEEDUP_DOMAIN, false) - if (speedupDomain) { - jObj.optJSONObject("routing") - .optJSONObject("settings") - .optJSONArray("rules") - .put(0, ruleDirectDnsObj) - } +// val speedupDomain = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEEDUP_DOMAIN, false) +// if (speedupDomain) { +// jObj.optJSONObject("routing") +// .optJSONObject("settings") +// .optJSONArray("rules") +// .put(0, ruleDirectDnsObj) +// } return jObj.toString() } catch (e: Exception) { diff --git a/V2rayNG/app/src/main/res/layout/activity_server.xml b/V2rayNG/app/src/main/res/layout/activity_server.xml index 434da39..1630abc 100644 --- a/V2rayNG/app/src/main/res/layout/activity_server.xml +++ b/V2rayNG/app/src/main/res/layout/activity_server.xml @@ -203,6 +203,25 @@ android:inputType="text" /> + + + + + + + + 传输协议(network) 功能设置(不清楚则保持默认值) 伪装类型(type) - 伪装域名/ws path/ws(path+host)分号(;)隔开 + 伪装域名host(host/ws host/h2 host) + path(ws path/h2 path) 底层传输安全 成功 失败 @@ -91,6 +92,7 @@ 错误,真实性验证失败. 感谢您的捐赠! 捐赠 + Paypal Logcat 复制 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index a3569da..a0bc2bb 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -35,7 +35,8 @@ 網路 更多功能 標頭類型 - 要求 主機/ws 路徑/ws(路徑;主機) + 要求 主機(host/ws host/h2 host) + path(ws path/h2 path) 傳輸層安全性 成功 失敗 @@ -92,6 +93,7 @@ 購買錯誤,真實性驗證失敗。 感謝您的捐款! 捐款 + Paypal Logcat 複製 diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 35835eb..c1ef9c0 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -11,6 +11,7 @@ tcp kcp ws + h2 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 58ce889..4d3667e 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -35,7 +35,8 @@ network more function head type - request host/ws path/ws(path;host) + request host(host/ws host/h2 host) + path(ws path/h2 path) tls Success Failure @@ -92,6 +93,7 @@ Error Purchase,Authenticity verification failed. Thank you for your donation! Donate + Paypal Logcat Copy diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 989e2af..083a509 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -21,10 +21,10 @@ android:summary="@string/summary_pref_remote_dns" android:title="@string/title_pref_remote_dns" /> - + + + +
From a11cf9b903d7e5cc874db922c1c626f86329e0c9 Mon Sep 17 00:00:00 2001 From: 2dust Date: Thu, 7 Jun 2018 13:22:03 +0800 Subject: [PATCH 24/49] General update --- .../helper/SimpleItemTouchHelperCallback.java | 2 +- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 42 ++++-- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 10 +- .../com/v2ray/ang/ui/PerAppProxyActivity.kt | 1 + .../com/v2ray/ang/ui/PerAppProxyAdapter.kt | 12 +- .../com/v2ray/ang/util/AngConfigManager.kt | 139 +++++++++++++----- .../main/kotlin/com/v2ray/ang/util/Utils.kt | 4 +- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 38 +++-- .../main/res/drawable/ic_share_white_24dp.xml | 9 ++ .../app/src/main/res/layout/item_qrcode.xml | 5 +- V2rayNG/app/src/main/res/menu/menu_main.xml | 5 + .../src/main/res/values-zh-rCN/strings.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 1 + V2rayNG/app/src/main/res/values/arrays.xml | 2 + V2rayNG/app/src/main/res/values/strings.xml | 1 + 15 files changed, 203 insertions(+), 69 deletions(-) create mode 100644 V2rayNG/app/src/main/res/drawable/ic_share_white_24dp.xml diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java index 2d281d5..e3c78b9 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.java @@ -48,7 +48,7 @@ public boolean isLongPressDragEnabled() { @Override public boolean isItemViewSwipeEnabled() { - return false; + return true; } @Override diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 86060d6..f8532bc 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -168,6 +168,14 @@ class MainActivity : BaseActivity() { importQRcode(REQUEST_SCAN_URL) true } + R.id.export_all -> { + if (AngConfigManager.shareAll2Clipboard() == 0) { + toast(R.string.toast_success) + } else { + toast(R.string.toast_failure) + } + true + } R.id.settings -> { startActivity("isRunning" to isRunning) true @@ -216,15 +224,31 @@ class MainActivity : BaseActivity() { } fun importConfig(server: String?) { - if (server == null) { - return - } - val resId = AngConfigManager.importConfig(server) - if (resId > 0) { - toast(resId) - } else { - toast(R.string.toast_success) - adapter.updateConfigList() + try { + if (server == null) { + return + } + var servers = server + if (server.indexOf("vmess") == server.lastIndexOf("vmess")) { + servers = server.replace("\n", "") + } + + var count = 0 + servers.lines() + .forEach { + val resId = AngConfigManager.importConfig(it) + if (resId == 0) { + count++ + } + } + if (count > 0) { + toast(R.string.toast_success) + adapter.updateConfigList() + } else { + toast(R.string.toast_failure) + } + } catch (e: Exception) { + e.printStackTrace() } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index aabe391..d751abc 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -119,7 +119,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter return MainViewHolder(parent.context.layoutInflater .inflate(R.layout.item_recycler_main, parent, false)) @@ -168,8 +168,12 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter, bl private var mActivity: BaseActivity = activity val blacklist = if (blacklist == null) HashSet() else HashSet(blacklist) - override fun onBindViewHolder(holder: BaseViewHolder?, position: Int) { + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { if (holder is AppViewHolder) { val appInfo = apps[position - 1] holder.bind(appInfo) @@ -32,7 +32,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl override fun getItemCount() = apps.size + 1 - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder? { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { val ctx = parent.context return when (viewType) { @@ -42,16 +42,16 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List, bl ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 3) BaseViewHolder(view) } +// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater +// .inflate(R.layout.item_recycler_bypass_list, parent, false)) - VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater + else -> AppViewHolder(ctx.layoutInflater .inflate(R.layout.item_recycler_bypass_list, parent, false)) - else -> null } } - override fun getItemViewType(position: Int) - = if (position == 0) VIEW_TYPE_HEADER else VIEW_TYPE_ITEM + override fun getItemViewType(position: Int) = if (position == 0) VIEW_TYPE_HEADER else VIEW_TYPE_ITEM open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 21dfeb8..87ee1db 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -14,6 +14,7 @@ import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.VmessQRCode import java.util.* + object AngConfigManager { private lateinit var app: AngApplication private lateinit var angConfig: AngConfig @@ -178,8 +179,9 @@ object AngConfigManager { if (angConfig.index < 0 || angConfig.vmess.count() <= 0 || angConfig.index > angConfig.vmess.count() - 1 - ) + ) { return "" + } return angConfig.vmess[angConfig.index].remarks } @@ -187,8 +189,9 @@ object AngConfigManager { if (angConfig.index < 0 || angConfig.vmess.count() <= 0 || angConfig.index > angConfig.vmess.count() - 1 - ) + ) { return "" + } return angConfig.vmess[angConfig.index].guid } @@ -203,37 +206,45 @@ object AngConfigManager { if (server.indexOf(VMESS_PROTOCOL) < 0) { return R.string.toast_incorrect_protocol } - var result = server.replace(VMESS_PROTOCOL, "") - result = Utils.decode(result) - if (TextUtils.isEmpty(result)) { - return R.string.toast_decoding_failed - } - val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) - if (TextUtils.isEmpty(vmessQRCode.add) - || TextUtils.isEmpty(vmessQRCode.port) - || TextUtils.isEmpty(vmessQRCode.id) - || TextUtils.isEmpty(vmessQRCode.aid) - || TextUtils.isEmpty(vmessQRCode.net) - ) { - return R.string.toast_incorrect_protocol - } - val vmess = AngConfig.VmessBean() - vmess.security = "chacha20-poly1305" - vmess.network = "tcp" - vmess.headerType = "none" - - vmess.configVersion = Utils.parseInt(vmessQRCode.v) - vmess.remarks = vmessQRCode.ps - vmess.address = vmessQRCode.add - vmess.port = Utils.parseInt(vmessQRCode.port) - vmess.id = vmessQRCode.id - vmess.alterId = Utils.parseInt(vmessQRCode.aid) - vmess.network = vmessQRCode.net - vmess.headerType = vmessQRCode.type - vmess.requestHost = vmessQRCode.host - vmess.path = vmessQRCode.path - vmess.streamSecurity = vmessQRCode.tls + var vmess = AngConfig.VmessBean() + val indexSplit = server.indexOf("?") + if (indexSplit > 0) { + vmess = ResolveVmess4Kitsunebi(server) + } else { + + var result = server.replace(VMESS_PROTOCOL, "") + result = Utils.decode(result) + if (TextUtils.isEmpty(result)) { + return R.string.toast_decoding_failed + } + val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) + if (TextUtils.isEmpty(vmessQRCode.add) + || TextUtils.isEmpty(vmessQRCode.port) + || TextUtils.isEmpty(vmessQRCode.id) + || TextUtils.isEmpty(vmessQRCode.aid) + || TextUtils.isEmpty(vmessQRCode.net) + ) { + return R.string.toast_incorrect_protocol + } + +// val vmess = AngConfig.VmessBean() + vmess.security = "chacha20-poly1305" + vmess.network = "tcp" + vmess.headerType = "none" + + vmess.configVersion = Utils.parseInt(vmessQRCode.v) + vmess.remarks = vmessQRCode.ps + vmess.address = vmessQRCode.add + vmess.port = Utils.parseInt(vmessQRCode.port) + vmess.id = vmessQRCode.id + vmess.alterId = Utils.parseInt(vmessQRCode.aid) + vmess.network = vmessQRCode.net + vmess.headerType = vmessQRCode.type + vmess.requestHost = vmessQRCode.host + vmess.path = vmessQRCode.path + vmess.streamSecurity = vmessQRCode.tls + } upgradeServerVersion(vmess) @@ -245,6 +256,41 @@ object AngConfigManager { return 0 } + private fun ResolveVmess4Kitsunebi(server: String): AngConfig.VmessBean { + + val vmess = AngConfig.VmessBean() + + var result = server.replace(VMESS_PROTOCOL, "") + val indexSplit = result.indexOf("?") + if (indexSplit > 0) { + result = result.substring(0, indexSplit) + } + result = Utils.decode(result) + + val arr1 = result.split('@') + if (arr1.count() != 2) { + return vmess + } + val arr21 = arr1[0].split(':') + val arr22 = arr1[1].split(':') + if (arr21.count() != 2 || arr21.count() != 2) { + return vmess + } + + vmess.address = arr22[0] + vmess.port = Utils.parseInt(arr22[1]) + vmess.security = arr21[0] + vmess.id = arr21[1] + + vmess.security = "chacha20-poly1305" + vmess.network = "tcp" + vmess.headerType = "none" + vmess.remarks = "Alien" + vmess.alterId = 0 + + return vmess + } + /** * share config */ @@ -253,6 +299,10 @@ object AngConfigManager { if (index < 0 || index > angConfig.vmess.count() - 1) { return "" } + if (angConfig.vmess[index].configType != 1) { + return "" + } + val vmess = angConfig.vmess[index] val vmessQRCode = VmessQRCode() vmessQRCode.v = vmess.configVersion.toString() @@ -295,6 +345,30 @@ object AngConfigManager { return 0 } + /** + * share2Clipboard + */ + fun shareAll2Clipboard(): Int { + try { + val sb = StringBuilder() + for (k in 0 until angConfig.vmess.count()) { + val url = shareConfig(k) + if (TextUtils.isEmpty(url)) { + continue + } + sb.append(url) + sb.appendln() + } + if (sb.count() > 0) { + Utils.setClipboard(app.applicationContext, sb.toString()) + } + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } + /** * share2QRCode */ @@ -374,7 +448,6 @@ object AngConfigManager { e.printStackTrace() return -1 } - return 0 } /** diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 57f6a8f..2930685 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -104,7 +104,7 @@ object Utils { */ fun encode(text: String): String { try { - return Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.DEFAULT) + return Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP) } catch (e: Exception) { e.printStackTrace() return "" @@ -144,7 +144,7 @@ object Utils { /** * create qrcode using zxing */ - fun createQRCode(text: String, size: Int = 500): Bitmap? { + fun createQRCode(text: String, size: Int = 800): Bitmap? { try { val hints = HashMap() hints.put(EncodeHintType.CHARACTER_SET, "utf-8") diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 12fd506..f499a9f 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.util +import android.os.Build import android.text.TextUtils import android.util.Log import com.google.gson.Gson @@ -98,7 +99,7 @@ object V2rayConfigUtil { if (config.index < 0 || config.vmess.count() <= 0 || config.index > config.vmess.count() - 1 - ) { + ) { return result } @@ -125,7 +126,7 @@ object V2rayConfigUtil { if (config.index < 0 || config.vmess.count() <= 0 || config.index > config.vmess.count() - 1 - ) { + ) { return result } @@ -175,7 +176,7 @@ object V2rayConfigUtil { if (config.index < 0 || config.vmess.count() <= 0 || config.index > config.vmess.count() - 1 - ) { + ) { return result } val vmess = config.vmess[config.index] @@ -216,16 +217,24 @@ object V2rayConfigUtil { v2rayConfig.outbound.streamSettings = boundStreamSettings(config) //如果非ip - if (!Utils.isIpAddress(vmess.address)) { - val addr = String.format("%s:%s", vmess.address, vmess.port) - val domainName = lib2rayObj.optJSONObject("preparedDomainName") - .optJSONArray("domainName") - if (domainName.length() > 0) { - for (index in 0 until domainName.length()) { - domainName.remove(index) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (!Utils.isIpAddress(vmess.address)) { + val addr = String.format("%s:%s", vmess.address, vmess.port) + val domainName = lib2rayObj.optJSONObject("preparedDomainName") + .optJSONArray("domainName") + if (domainName.length() > 0) { + for (index in 0 until domainName.length()) { + domainName.remove(index) + } } + domainName.put(addr) + } + } else { + if (!Utils.isIpAddress(vmess.address)) { + lib2rayObj.optJSONObject("preparedDomainName") + .optJSONArray("domainName") + .put(String.format("%s:%s", vmess.address, vmess.port)) } - domainName.put(addr) } } catch (e: Exception) { e.printStackTrace() @@ -413,9 +422,12 @@ object V2rayConfigUtil { userRule .split(",") .forEach { - if (Utils.isIpAddress(it)) { + if (Utils.isIpAddress(it) || it.startsWith("geoip:")) { rulesIP.ip?.add(it) - } else if (Utils.isValidUrl(it)) { + } else if (Utils.isValidUrl(it) + || it.startsWith("geosite:") + || it.startsWith("regexp:") + || it.startsWith("domain:")) { rulesDomain.domain?.add(it) } } diff --git a/V2rayNG/app/src/main/res/drawable/ic_share_white_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_share_white_24dp.xml new file mode 100644 index 0000000..9040666 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_share_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/layout/item_qrcode.xml b/V2rayNG/app/src/main/res/layout/item_qrcode.xml index 20278c3..3c53162 100644 --- a/V2rayNG/app/src/main/res/layout/item_qrcode.xml +++ b/V2rayNG/app/src/main/res/layout/item_qrcode.xml @@ -8,7 +8,8 @@ diff --git a/V2rayNG/app/src/main/res/menu/menu_main.xml b/V2rayNG/app/src/main/res/menu/menu_main.xml index e43b464..da591c3 100644 --- a/V2rayNG/app/src/main/res/menu/menu_main.xml +++ b/V2rayNG/app/src/main/res/menu/menu_main.xml @@ -33,6 +33,11 @@ app:showAsAction="never" />
+ Logcat
复制 + 导出全部配置至剪贴板 启动服务 确定 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index a0bc2bb..91bf52b 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -97,6 +97,7 @@ Logcat 複製 + 匯出全部配置至剪貼簿 啟動服務 確定 diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index c1ef9c0..7042e2f 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -20,6 +20,7 @@ srtp utp wechat-video + dtls @@ -32,6 +33,7 @@ srtp utp wechat-video + dtls diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 4d3667e..8505b53 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -97,6 +97,7 @@ Logcat Copy + Export all config to clipboard Start Service Confirm From f5e10ec80a6369f618157d27888a690d0af3fc79 Mon Sep 17 00:00:00 2001 From: 2dust Date: Fri, 6 Jul 2018 09:40:01 +0800 Subject: [PATCH 25/49] update --- V2rayNG/app/src/main/AndroidManifest.xml | 22 +- V2rayNG/app/src/main/assets/v2ray_config.json | 10 + .../main/kotlin/com/v2ray/ang/AppConfig.kt | 6 + .../kotlin/com/v2ray/ang/dto/AngConfig.kt | 12 +- .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 13 +- .../com/v2ray/ang/service/V2RayVpnService.kt | 12 +- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 92 ++++-- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 29 +- .../com/v2ray/ang/ui/ScScannerActivity.kt | 52 +++ .../com/v2ray/ang/ui/Server2Activity.kt | 2 +- .../com/v2ray/ang/ui/Server3Activity.kt | 175 +++++++++++ .../kotlin/com/v2ray/ang/ui/ServerActivity.kt | 7 +- .../com/v2ray/ang/ui/SettingsActivity.kt | 7 + .../com/v2ray/ang/ui/SubSettingActivity.kt | 98 ++++++ .../com/v2ray/ang/util/AngConfigManager.kt | 295 ++++++++++++++---- .../main/kotlin/com/v2ray/ang/util/Utils.kt | 33 ++ .../com/v2ray/ang/util/V2rayConfigUtil.kt | 56 +++- .../res/drawable/ic_qu_scan_black_24dp.xml | 19 ++ .../drawable/ic_qu_settings_black_24dp.xml | 12 + .../main/res/drawable/ic_scan_black_24dp.xml | 19 ++ .../drawable/ic_subscriptions_black_24dp.xml | 9 + .../drawable/ic_subscriptions_white_24dp.xml | 9 + .../src/main/res/layout/activity_server3.xml | 129 ++++++++ .../main/res/layout/activity_sub_setting.xml | 250 +++++++++++++++ .../main/res/layout/item_recycler_main.xml | 64 ++-- V2rayNG/app/src/main/res/menu/menu_main.xml | 18 +- .../src/main/res/values-zh-rCN/strings.xml | 12 +- .../src/main/res/values-zh-rTW/strings.xml | 12 +- V2rayNG/app/src/main/res/values/arrays.xml | 10 + V2rayNG/app/src/main/res/values/strings.xml | 12 +- .../app/src/main/res/xml/pref_settings.xml | 4 + V2rayNG/app/src/main/res/xml/shortcuts.xml | 31 ++ 32 files changed, 1379 insertions(+), 152 deletions(-) create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ScScannerActivity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server3Activity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt create mode 100644 V2rayNG/app/src/main/res/drawable/ic_qu_scan_black_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_scan_black_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_subscriptions_black_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_subscriptions_white_24dp.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_server3.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_sub_setting.xml create mode 100644 V2rayNG/app/src/main/res/xml/shortcuts.xml diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index d5725ad..d01fb8e 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -7,8 +7,8 @@ - + + + + - + + + - + + - + - + diff --git a/V2rayNG/app/src/main/assets/v2ray_config.json b/V2rayNG/app/src/main/assets/v2ray_config.json index d3adda5..c7a41cf 100644 --- a/V2rayNG/app/src/main/assets/v2ray_config.json +++ b/V2rayNG/app/src/main/assets/v2ray_config.json @@ -31,6 +31,16 @@ } ] } + ], + "servers": [ + { + "address": "v2ray.cool", + "method": "chacha20", + "ota": false, + "password": "123456", + "port": 10086, + "level": 1 + } ] }, "streamSettings": { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt index e4efdf6..c169b50 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt @@ -11,6 +11,7 @@ object AppConfig { const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid" const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name" const val VMESS_PROTOCOL: String = "vmess://" + const val SS_PROTOCOL: String = "ss://" 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" @@ -39,5 +40,10 @@ object AppConfig { const val MSG_STATE_RESTART = 5 const val MSG_STATE_RESTART_SOFT = 6 + object EConfigType { + val Vmess = 1 + val Custom = 2 + val Shadowsocks = 3 + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt index 202b568..e1ada59 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/AngConfig.kt @@ -2,7 +2,8 @@ package com.v2ray.ang.dto data class AngConfig( var index: Int, - var vmess: ArrayList + var vmess: ArrayList, + var subItem: ArrayList ) { data class VmessBean(var guid: String = "123456", var address: String = "v2ray.cool", @@ -17,5 +18,10 @@ data class AngConfig( var path: String = "", var streamSecurity: String = "", var configType: Int = 1, - var configVersion: Int = 1) -} \ No newline at end of file + var configVersion: Int = 1, + var subid: String = "") + + data class SubItemBean(var id: String = "", + var remarks: String = "", + var url: String = "") +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt index ebfb8fb..4e10ff7 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt @@ -23,12 +23,13 @@ data class V2rayConfig(val port: Int, } data class OutboundBean(val tag: String, - val protocol: String, + var protocol: String, var settings: OutSettingsBean, var streamSettings: StreamSettingsBean, var mux: MuxBean) { - data class OutSettingsBean(var vnext: List) { + data class OutSettingsBean(var vnext: List?, + var servers: List?) { data class VnextBean(var address: String, var port: Int, @@ -38,6 +39,14 @@ data class V2rayConfig(val port: Int, var alterId: Int, var security: String) } + + data class ServersBean(var address: String, + var method: String, + var ota: Boolean, + var password: String, + var port: Int, + var level: Int) + } data class StreamSettingsBean(var network: String, diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 1bcd663..163223f 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -26,6 +26,8 @@ import com.v2ray.ang.util.Utils import libv2ray.Libv2ray import libv2ray.V2RayCallbacks import libv2ray.V2RayVPNServiceSupportsSet +import java.io.FileDescriptor +import java.io.PrintWriter import java.lang.ref.SoftReference class V2RayVpnService : VpnService() { @@ -66,6 +68,12 @@ class V2RayVpnService : VpnService() { stopV2Ray() } + override fun onDestroy() { + super.onDestroy() + + cancelNotification() + } + fun setup(parameters: String) { // If the old interface has exactly the same parameters, use it! // Configure a builder while parsing the parameters. @@ -134,7 +142,7 @@ class V2RayVpnService : VpnService() { v2rayPoint.vpnSupportReady() if (v2rayPoint.isRunning) { MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "") -// showNotification() + showNotification() } else { MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "") cancelNotification() @@ -179,7 +187,7 @@ class V2RayVpnService : VpnService() { v2rayPoint.runLoop() } - showNotification() +// showNotification() } private fun stopV2Ray(isForced: Boolean = true) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index f8532bc..91ef89f 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -23,6 +23,7 @@ import java.lang.ref.SoftReference import java.net.URL import android.content.IntentFilter import android.support.v7.widget.helper.ItemTouchHelper +import android.util.Log import rx.Observable import rx.android.schedulers.AndroidSchedulers import java.util.concurrent.TimeUnit @@ -122,7 +123,7 @@ class MainActivity : BaseActivity() { } REQUEST_SCAN -> if (resultCode == RESULT_OK) { - importConfig(data?.getStringExtra("SCAN_RESULT")) + importBatchConfig(data?.getStringExtra("SCAN_RESULT")) } REQUEST_FILE_CHOOSER -> { if (resultCode == RESULT_OK) { @@ -151,11 +152,16 @@ class MainActivity : BaseActivity() { importClipboard() true } - R.id.import_manually -> { + R.id.import_manually_vmess -> { startActivity("position" to -1, "isRunning" to isRunning) adapter.updateConfigList() true } + R.id.import_manually_ss -> { + startActivity("position" to -1, "isRunning" to isRunning) + adapter.updateConfigList() + true + } R.id.import_config_custom_local -> { importConfigCustomLocal() true @@ -168,6 +174,17 @@ class MainActivity : BaseActivity() { importQRcode(REQUEST_SCAN_URL) true } + + R.id.sub_setting -> { + startActivity() + true + } + + R.id.sub_update -> { + importConfigViaSub() + true + } + R.id.export_all -> { if (AngConfigManager.shareAll2Clipboard() == 0) { toast(R.string.toast_success) @@ -215,7 +232,7 @@ class MainActivity : BaseActivity() { fun importClipboard(): Boolean { try { val clipboard = Utils.getClipboard(this) - importConfig(clipboard) + importBatchConfig(clipboard) } catch (e: Exception) { e.printStackTrace() return false @@ -223,32 +240,13 @@ class MainActivity : BaseActivity() { return true } - fun importConfig(server: String?) { - try { - if (server == null) { - return - } - var servers = server - if (server.indexOf("vmess") == server.lastIndexOf("vmess")) { - servers = server.replace("\n", "") - } - - var count = 0 - servers.lines() - .forEach { - val resId = AngConfigManager.importConfig(it) - if (resId == 0) { - count++ - } - } - if (count > 0) { - toast(R.string.toast_success) - adapter.updateConfigList() - } else { - toast(R.string.toast_failure) - } - } catch (e: Exception) { - e.printStackTrace() + 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) } } @@ -301,6 +299,40 @@ class MainActivity : BaseActivity() { return true } + /** + * 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) + } + } + } + } catch (e: Exception) { + e.printStackTrace() + return false + } + return true + } + /** * show file chooser */ @@ -409,7 +441,7 @@ class MainActivity : BaseActivity() { fun hideCircle() { try { - Observable.timer(500, TimeUnit.MILLISECONDS) + Observable.timer(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { if (fabProgressCircle.isShown) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index d751abc..fb7a822 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -2,8 +2,11 @@ package com.v2ray.ang.ui import android.graphics.Color import android.support.v7.widget.RecyclerView +import android.text.TextUtils +import android.view.TextureView import android.view.View import android.view.ViewGroup +import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.helper.ItemTouchHelperAdapter @@ -47,7 +50,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter("position" to position, "isRunning" to !changeable) - } else if (configType == 2) { + } else if (configType == AppConfig.EConfigType.Custom) { mActivity.startActivity("position" to position, "isRunning" to !changeable) + } else if (configType == AppConfig.EConfigType.Shadowsocks) { + mActivity.startActivity("position" to position, "isRunning" to !changeable) } } @@ -149,6 +165,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter(requestCode) + else + toast(R.string.toast_permission_denied) + } + + return true + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + REQUEST_SCAN -> + if (resultCode == RESULT_OK) { + val count = AngConfigManager.importBatchConfig(data?.getStringExtra("SCAN_RESULT"), "") + if (count > 0) { + toast(R.string.toast_success) + } else { + toast(R.string.toast_failure) + } + startActivity() + } + } + finish() + } + +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt index e19ca29..4fef0cf 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server2Activity.kt @@ -75,7 +75,7 @@ class Server2Activity : BaseActivity() { return false } - if (AngConfigManager.addServer(vmess, edit_index) == 0) { + if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) { toast(R.string.toast_success) finish() return true diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server3Activity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server3Activity.kt new file mode 100644 index 0000000..92ee6e2 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/Server3Activity.kt @@ -0,0 +1,175 @@ +package com.v2ray.ang.ui + +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import com.v2ray.ang.R +import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.util.AngConfigManager +import com.v2ray.ang.util.Utils +import kotlinx.android.synthetic.main.activity_server3.* +import org.jetbrains.anko.* + + +class Server3Activity : BaseActivity() { + companion object { + private const val REQUEST_SCAN = 1 + } + + var del_config: MenuItem? = null + var save_config: MenuItem? = null + + private lateinit var configs: AngConfig + private var edit_index: Int = -1 //当前编辑的服务器 + private var edit_guid: String = "" + private var isRunning: Boolean = false + private val securitys: Array by lazy { + resources.getStringArray(R.array.ss_securitys) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_server3) + + configs = AngConfigManager.configs + edit_index = intent.getIntExtra("position", -1) + isRunning = intent.getBooleanExtra("isRunning", false) + title = getString(R.string.title_server) + + if (edit_index >= 0) { + edit_guid = configs.vmess[edit_index].guid + bindingServer(configs.vmess[edit_index]) + } else { + clearServer() + } + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + /** + * bingding seleced server config + */ + fun bindingServer(vmess: AngConfig.VmessBean): Boolean { + et_remarks.text = Utils.getEditable(vmess.remarks) + + et_address.text = Utils.getEditable(vmess.address) + et_port.text = Utils.getEditable(vmess.port.toString()) + et_id.text = Utils.getEditable(vmess.id) + val security = Utils.arrayFind(securitys, vmess.security) + if (security >= 0) { + sp_security.setSelection(security) + } + + return true + } + + /** + * clear or init server config + */ + fun clearServer(): Boolean { + et_remarks.text = null + et_address.text = null + et_port.text = Utils.getEditable("10086") + et_id.text = null + sp_security.setSelection(0) + + return true + } + + /** + * save server config + */ + fun saveServer(): Boolean { + val vmess: AngConfig.VmessBean + if (edit_index >= 0) { + vmess = configs.vmess[edit_index] + } else { + vmess = AngConfig.VmessBean() + } + + vmess.guid = edit_guid + vmess.remarks = et_remarks.text.toString() + vmess.address = et_address.text.toString() + vmess.port = Utils.parseInt(et_port.text.toString()) + vmess.id = et_id.text.toString() + vmess.security = securitys[sp_security.selectedItemPosition] + + if (TextUtils.isEmpty(vmess.remarks)) { + toast(R.string.server_lab_remarks) + return false + } + if (TextUtils.isEmpty(vmess.address)) { + toast(R.string.server_lab_address3) + return false + } + if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) { + toast(R.string.server_lab_port3) + return false + } + if (TextUtils.isEmpty(vmess.id)) { + toast(R.string.server_lab_id3) + return false + } + + if (AngConfigManager.addShadowsocksServer(vmess, edit_index) == 0) { + toast(R.string.toast_success) + finish() + return true + } else { + toast(R.string.toast_failure) + return false + } + } + + /** + * save server config + */ + fun deleteServer(): Boolean { + if (edit_index >= 0) { + alert(R.string.del_config_comfirm) { + positiveButton(android.R.string.ok) { + if (AngConfigManager.removeServer(edit_index) == 0) { + toast(R.string.toast_success) + finish() + } else { + toast(R.string.toast_failure) + } + } + show() + } + } else { + } + return true + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.action_server, menu) + del_config = menu?.findItem(R.id.del_config) + save_config = menu?.findItem(R.id.save_config) + + if (edit_index >= 0) { + if (isRunning) { + if (edit_index == configs.index) { + del_config?.isVisible = false + save_config?.isVisible = false + } + } + } else { + del_config?.isVisible = false + } + + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.del_config -> { + deleteServer() + true + } + R.id.save_config -> { + saveServer() + true + } + else -> super.onOptionsItemSelected(item) + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt index 7690e94..d2a1403 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ServerActivity.kt @@ -112,7 +112,12 @@ class ServerActivity : BaseActivity() { * save server config */ fun saveServer(): Boolean { - val vmess: AngConfig.VmessBean = AngConfig.VmessBean() + val vmess: AngConfig.VmessBean + if (edit_index >= 0) { + vmess = configs.vmess[edit_index] + } else { + vmess = AngConfig.VmessBean() + } vmess.guid = edit_guid vmess.remarks = et_remarks.text.toString() diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 38416d3..94b0ad4 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -33,6 +33,7 @@ class SettingsActivity : BaseActivity() { const val PREF_DONATE = "pref_donate" // const val PREF_LICENSES = "pref_licenses" const val PREF_FEEDBACK = "pref_feedback" + const val PREF_TG_GROUP = "pref_tg_group" const val PREF_VERSION = "pref_version" // const val PREF_AUTO_RESTART = "pref_auto_restart" } @@ -53,6 +54,7 @@ class SettingsActivity : BaseActivity() { val donate: Preference by lazy { findPreference(PREF_DONATE) } // val licenses: Preference by lazy { findPreference(PREF_LICENSES) } val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } + val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) } val version: Preference by lazy { findPreference(PREF_VERSION) } override fun onCreate(savedInstanceState: Bundle?) { @@ -78,6 +80,11 @@ class SettingsActivity : BaseActivity() { feedback.onClick { Utils.openUri(activity, "https://github.com/2dust/v2rayNG/issues") } + tgGroup.onClick { + // Utils.openUri(activity, "https://t.me/v2rayN") + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("tg:resolve?domain=v2rayN")) + startActivity(intent) + } perAppProxy.setOnPreferenceClickListener { startActivity() diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt new file mode 100644 index 0000000..3b50209 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt @@ -0,0 +1,98 @@ +package com.v2ray.ang.ui + +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import com.v2ray.ang.R +import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.util.AngConfigManager +import com.v2ray.ang.util.Utils +import kotlinx.android.synthetic.main.activity_sub_setting.* +import org.jetbrains.anko.* + + +class SubSettingActivity : BaseActivity() { + + var del_config: MenuItem? = null + var save_config: MenuItem? = null + + private lateinit var configs: AngConfig + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_sub_setting) + + configs = AngConfigManager.configs + title = getString(R.string.title_sub_setting) + + bindingServer() + + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + /** + * bingding seleced server config + */ + fun bindingServer(): Boolean { + for (k in configs.subItem.count()..2) { + val subItem = AngConfig.SubItemBean() + configs.subItem.add(subItem) + } + + et_remarks.text = Utils.getEditable(configs.subItem[0].remarks) + et_url.text = Utils.getEditable(configs.subItem[0].url) + + et_remarks2.text = Utils.getEditable(configs.subItem[1].remarks) + et_url2.text = Utils.getEditable(configs.subItem[1].url) + + et_remarks3.text = Utils.getEditable(configs.subItem[2].remarks) + et_url3.text = Utils.getEditable(configs.subItem[2].url) + + return true + } + + /** + * save server config + */ + fun saveServer(): Boolean { + + configs.subItem[0].remarks = et_remarks.text.toString().trim() + configs.subItem[0].url = et_url.text.toString().trim() + + configs.subItem[1].remarks = et_remarks2.text.toString().trim() + configs.subItem[1].url = et_url2.text.toString().trim() + + configs.subItem[2].remarks = et_remarks3.text.toString().trim() + configs.subItem[2].url = et_url3.text.toString().trim() + + + if (AngConfigManager.saveSubItem(configs.subItem) == 0) { + toast(R.string.toast_success) + finish() + return true + } else { + toast(R.string.toast_failure) + return false + } + } + + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.action_server, menu) + del_config = menu?.findItem(R.id.del_config) + save_config = menu?.findItem(R.id.save_config) + + del_config?.isVisible = false + + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.save_config -> { + saveServer() + true + } + else -> super.onOptionsItemSelected(item) + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 87ee1db..6401a52 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -2,16 +2,20 @@ package com.v2ray.ang.util import android.graphics.Bitmap import android.text.TextUtils +import android.util.Log import com.google.gson.Gson import com.v2ray.ang.AngApplication +import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.ANG_CONFIG import com.v2ray.ang.AppConfig.PREF_CURR_CONFIG import com.v2ray.ang.AppConfig.PREF_CURR_CONFIG_GUID import com.v2ray.ang.AppConfig.PREF_CURR_CONFIG_NAME +import com.v2ray.ang.AppConfig.SS_PROTOCOL import com.v2ray.ang.AppConfig.VMESS_PROTOCOL import com.v2ray.ang.R import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.VmessQRCode +import java.net.URLDecoder import java.util.* @@ -36,13 +40,17 @@ object AngConfigManager { if (!TextUtils.isEmpty(context)) { angConfig = Gson().fromJson(context, AngConfig::class.java) } else { - angConfig = AngConfig(0, vmess = arrayListOf(AngConfig.VmessBean())) + angConfig = AngConfig(0, vmess = arrayListOf(AngConfig.VmessBean()), subItem = arrayListOf(AngConfig.SubItemBean())) } for (i in angConfig.vmess.indices) { upgradeServerVersion(angConfig.vmess[i]) } + if (configs.subItem == null) { + configs.subItem = arrayListOf(AngConfig.SubItemBean()) + } + } catch (e: Exception) { e.printStackTrace() } @@ -54,13 +62,14 @@ object AngConfigManager { fun addServer(vmess: AngConfig.VmessBean, index: Int): Int { try { vmess.configVersion = 2 + vmess.configType = AppConfig.EConfigType.Vmess if (index >= 0) { //edit angConfig.vmess[index] = vmess } else { //add - vmess.guid = System.currentTimeMillis().toString() + vmess.guid = Utils.getUuid() angConfig.vmess.add(vmess) if (angConfig.vmess.count() == 1) { angConfig.index = 0 @@ -198,57 +207,98 @@ object AngConfigManager { /** * import config form qrcode or... */ - fun importConfig(server: String?): Int { + fun importConfig(server: String?, subid: String): Int { try { if (server == null || TextUtils.isEmpty(server)) { return R.string.toast_none_data } - if (server.indexOf(VMESS_PROTOCOL) < 0) { - return R.string.toast_incorrect_protocol - } var vmess = AngConfig.VmessBean() - val indexSplit = server.indexOf("?") - if (indexSplit > 0) { - vmess = ResolveVmess4Kitsunebi(server) - } else { - var result = server.replace(VMESS_PROTOCOL, "") - result = Utils.decode(result) - if (TextUtils.isEmpty(result)) { - return R.string.toast_decoding_failed + if (server.startsWith(VMESS_PROTOCOL)) { + + val indexSplit = server.indexOf("?") + if (indexSplit > 0) { + vmess = ResolveVmess4Kitsunebi(server) + } else { + + var result = server.replace(VMESS_PROTOCOL, "") + result = Utils.decode(result) + if (TextUtils.isEmpty(result)) { + return R.string.toast_decoding_failed + } + val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) + if (TextUtils.isEmpty(vmessQRCode.add) + || TextUtils.isEmpty(vmessQRCode.port) + || TextUtils.isEmpty(vmessQRCode.id) + || TextUtils.isEmpty(vmessQRCode.aid) + || TextUtils.isEmpty(vmessQRCode.net) + ) { + return R.string.toast_incorrect_protocol + } + + vmess.configType = AppConfig.EConfigType.Vmess + vmess.security = "chacha20-poly1305" + vmess.network = "tcp" + vmess.headerType = "none" + + vmess.configVersion = Utils.parseInt(vmessQRCode.v) + vmess.remarks = vmessQRCode.ps + vmess.address = vmessQRCode.add + vmess.port = Utils.parseInt(vmessQRCode.port) + vmess.id = vmessQRCode.id + vmess.alterId = Utils.parseInt(vmessQRCode.aid) + vmess.network = vmessQRCode.net + vmess.headerType = vmessQRCode.type + vmess.requestHost = vmessQRCode.host + vmess.path = vmessQRCode.path + vmess.streamSecurity = vmessQRCode.tls + vmess.subid = subid } - val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java) - if (TextUtils.isEmpty(vmessQRCode.add) - || TextUtils.isEmpty(vmessQRCode.port) - || TextUtils.isEmpty(vmessQRCode.id) - || TextUtils.isEmpty(vmessQRCode.aid) - || TextUtils.isEmpty(vmessQRCode.net) - ) { - return R.string.toast_incorrect_protocol + upgradeServerVersion(vmess) + addServer(vmess, -1) + + } else if (server.startsWith(SS_PROTOCOL)) { + var result = server.replace(SS_PROTOCOL, "") + val indexSplit = result.indexOf("#") + if (indexSplit > 0) { + try { + vmess.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length)) + } catch (e: Exception) { + e.printStackTrace() + } + + result = result.substring(0, indexSplit) } -// val vmess = AngConfig.VmessBean() - vmess.security = "chacha20-poly1305" - vmess.network = "tcp" - vmess.headerType = "none" - - vmess.configVersion = Utils.parseInt(vmessQRCode.v) - vmess.remarks = vmessQRCode.ps - vmess.address = vmessQRCode.add - vmess.port = Utils.parseInt(vmessQRCode.port) - vmess.id = vmessQRCode.id - vmess.alterId = Utils.parseInt(vmessQRCode.aid) - vmess.network = vmessQRCode.net - vmess.headerType = vmessQRCode.type - vmess.requestHost = vmessQRCode.host - vmess.path = vmessQRCode.path - vmess.streamSecurity = vmessQRCode.tls - } + //part decode + val indexS = result.indexOf("@") + if (indexS > 0) { + result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length) + } else { + result = Utils.decode(result) + } + + val arr1 = result.split('@') + if (arr1.count() != 2) { + return R.string.toast_incorrect_protocol + } + val arr21 = arr1[0].split(':') + val arr22 = arr1[1].split(':') + if (arr21.count() != 2 || arr21.count() != 2) { + return R.string.toast_incorrect_protocol + } - upgradeServerVersion(vmess) + vmess.address = arr22[0] + vmess.port = Utils.parseInt(arr22[1]) + vmess.security = arr21[0] + vmess.id = arr21[1] + vmess.subid = subid - addServer(vmess, -1) + addShadowsocksServer(vmess, -1) + } else { + return R.string.toast_incorrect_protocol + } } catch (e: Exception) { e.printStackTrace() return -1 @@ -299,27 +349,37 @@ object AngConfigManager { if (index < 0 || index > angConfig.vmess.count() - 1) { return "" } - if (angConfig.vmess[index].configType != 1) { - return "" - } val vmess = angConfig.vmess[index] - val vmessQRCode = VmessQRCode() - vmessQRCode.v = vmess.configVersion.toString() - vmessQRCode.ps = vmess.remarks - vmessQRCode.add = vmess.address - vmessQRCode.port = vmess.port.toString() - vmessQRCode.id = vmess.id - vmessQRCode.aid = vmess.alterId.toString() - vmessQRCode.net = vmess.network - vmessQRCode.type = vmess.headerType - vmessQRCode.host = vmess.requestHost - vmessQRCode.path = vmess.path - vmessQRCode.tls = vmess.streamSecurity - val json = Gson().toJson(vmessQRCode) - val conf = VMESS_PROTOCOL + Utils.encode(json) - - return conf + if (angConfig.vmess[index].configType == AppConfig.EConfigType.Vmess) { + + val vmessQRCode = VmessQRCode() + vmessQRCode.v = vmess.configVersion.toString() + vmessQRCode.ps = vmess.remarks + vmessQRCode.add = vmess.address + vmessQRCode.port = vmess.port.toString() + vmessQRCode.id = vmess.id + vmessQRCode.aid = vmess.alterId.toString() + vmessQRCode.net = vmess.network + vmessQRCode.type = vmess.headerType + vmessQRCode.host = vmess.requestHost + vmessQRCode.path = vmess.path + vmessQRCode.tls = vmess.streamSecurity + val json = Gson().toJson(vmessQRCode) + val conf = VMESS_PROTOCOL + Utils.encode(json) + + return conf + } else if (angConfig.vmess[index].configType == AppConfig.EConfigType.Shadowsocks) { + val remark = "#" + Utils.urlEncode(vmess.remarks) + val url = String.format("%s:%s@%s:%s", + vmess.security, + vmess.id, + vmess.address, + vmess.port) + return SS_PROTOCOL + Utils.encode(url) + remark + } else { + return "" + } } catch (e: Exception) { e.printStackTrace() return "" @@ -402,7 +462,7 @@ object AngConfigManager { //add val vmess = AngConfig.VmessBean() vmess.configVersion = 2 - vmess.configType = 2 + vmess.configType = AppConfig.EConfigType.Custom vmess.guid = guid vmess.remarks = vmess.guid @@ -500,4 +560,117 @@ object AngConfigManager { return -1 } } + + + fun addCustomServer(vmess: AngConfig.VmessBean, index: Int): Int { + try { + vmess.configVersion = 2 + vmess.configType = AppConfig.EConfigType.Custom + + if (index >= 0) { + //edit + angConfig.vmess[index] = vmess + } else { + //add + vmess.guid = System.currentTimeMillis().toString() + angConfig.vmess.add(vmess) + if (angConfig.vmess.count() == 1) { + angConfig.index = 0 + } + } + + storeConfigFile() + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } + + fun addShadowsocksServer(vmess: AngConfig.VmessBean, index: Int): Int { + try { + vmess.configVersion = 2 + vmess.configType = AppConfig.EConfigType.Shadowsocks + + if (index >= 0) { + //edit + angConfig.vmess[index] = vmess + } else { + //add + vmess.guid = System.currentTimeMillis().toString() + angConfig.vmess.add(vmess) + if (angConfig.vmess.count() == 1) { + angConfig.index = 0 + } + } + + storeConfigFile() + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } + + fun importBatchConfig(server: String?, subid: String): Int { + try { + if (server == null) { + return 0 + } + removeServerViaSubid(subid) + + var servers = server + if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) { + servers = server.replace("\n", "") + } + + var count = 0 + servers.lines() + .forEach { + val resId = importConfig(it, subid) + if (resId == 0) { + count++ + } + } + return count + } catch (e: Exception) { + e.printStackTrace() + } + return 0 + } + + fun saveSubItem(subItem: ArrayList): Int { + try { + if (subItem.count() <= 0) { + return -1 + } + for (k in 0 until subItem.count()) { + if (TextUtils.isEmpty(subItem[k].id)) { + subItem[k].id = Utils.getUuid() + } + } + angConfig.subItem = subItem + + storeConfigFile() + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } + + fun removeServerViaSubid(subid: String): Int { + if (TextUtils.isEmpty(subid) || configs.vmess.count() <= 0) { + return -1 + } + + for (k in configs.vmess.count() - 1 downTo 0) { + if (configs.vmess[k].subid.equals(subid)) { + angConfig.vmess.removeAt(k) + } + } + + storeConfigFile() + return 0 + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 2930685..74f2b51 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -24,6 +24,9 @@ import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.ui.SettingsActivity import me.dozen.dpreference.DPreference import org.jetbrains.anko.toast +import java.net.URLDecoder +import java.net.URLEncoder + object Utils { @@ -290,6 +293,36 @@ object Utils { val uri = Uri.parse(uriString) context.startActivity(Intent(Intent.ACTION_VIEW, uri)) } + + /** + * uuid + */ + fun getUuid(): String { + try { + return UUID.randomUUID().toString().replace("-", "") + } catch (e: Exception) { + e.printStackTrace() + return "" + } + } + + fun urlDecode(url: String): String { + try { + return URLDecoder.decode(url, "UTF-8") + } catch (e: Exception) { + e.printStackTrace() + return url + } + } + + fun urlEncode(url: String): String { + try { + return URLEncoder.encode(url, "UTF-8") + } catch (e: Exception) { + e.printStackTrace() + return url + } + } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index f499a9f..ece5eec 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -103,10 +103,12 @@ object V2rayConfigUtil { return result } - if (config.vmess[config.index].configType == 1) { + if (config.vmess[config.index].configType == AppConfig.EConfigType.Vmess) { result = getV2rayConfigType1(app, config) - } else if (config.vmess[config.index].configType == 2) { + } else if (config.vmess[config.index].configType == AppConfig.EConfigType.Custom) { result = getV2rayConfigType2(app, config) + } else if (config.vmess[config.index].configType == AppConfig.EConfigType.Shadowsocks) { + result = getV2rayConfigType1(app, config) } Log.d("V2rayConfigUtil", result.content) return result @@ -202,19 +204,49 @@ object V2rayConfigUtil { private fun outbound(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { val vmess = config.vmess[config.index] - v2rayConfig.outbound.settings.vnext[0].address = vmess.address - v2rayConfig.outbound.settings.vnext[0].port = vmess.port - v2rayConfig.outbound.settings.vnext[0].users[0].id = vmess.id - v2rayConfig.outbound.settings.vnext[0].users[0].alterId = vmess.alterId - v2rayConfig.outbound.settings.vnext[0].users[0].security = vmess.security + when (vmess.configType) { + AppConfig.EConfigType.Vmess -> { + v2rayConfig.outbound.settings.servers = null - //Mux - val muxEnabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) - v2rayConfig.outbound.mux.enabled = muxEnabled + val vnext = v2rayConfig.outbound.settings.vnext?.get(0) + vnext?.address = vmess.address + vnext?.port = vmess.port + val user = vnext?.users?.get(0) + user?.id = vmess.id + user?.alterId = vmess.alterId + user?.security = vmess.security - //远程服务器底层传输配置 - v2rayConfig.outbound.streamSettings = boundStreamSettings(config) + //Mux + val muxEnabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) + v2rayConfig.outbound.mux.enabled = muxEnabled + + //远程服务器底层传输配置 + v2rayConfig.outbound.streamSettings = boundStreamSettings(config) + + v2rayConfig.outbound.protocol = "vmess" + + } + AppConfig.EConfigType.Shadowsocks -> { + v2rayConfig.outbound.settings.vnext = null + + val server = v2rayConfig.outbound.settings.servers?.get(0) + server?.address = vmess.address + server?.method = vmess.security + server?.ota = false + server?.password = vmess.id + server?.port = vmess.port + server?.level = 1 + + //Mux + v2rayConfig.outbound.mux.enabled = false + + v2rayConfig.outbound.protocol = "shadowsocks" + + } + else -> { + } + } //如果非ip if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { diff --git a/V2rayNG/app/src/main/res/drawable/ic_qu_scan_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_qu_scan_black_24dp.xml new file mode 100644 index 0000000..a31063b --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_qu_scan_black_24dp.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml new file mode 100644 index 0000000..9ff4872 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_scan_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_scan_black_24dp.xml new file mode 100644 index 0000000..a31063b --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_scan_black_24dp.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/drawable/ic_subscriptions_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_subscriptions_black_24dp.xml new file mode 100644 index 0000000..6f0ed45 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_subscriptions_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_subscriptions_white_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_subscriptions_white_24dp.xml new file mode 100644 index 0000000..bc20a83 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_subscriptions_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_server3.xml b/V2rayNG/app/src/main/res/layout/activity_server3.xml new file mode 100644 index 0000000..3f0af8f --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_server3.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml b/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml new file mode 100644 index 0000000..746415b --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/item_recycler_main.xml b/V2rayNG/app/src/main/res/layout/item_recycler_main.xml index ff2fade..76ef7ce 100644 --- a/V2rayNG/app/src/main/res/layout/item_recycler_main.xml +++ b/V2rayNG/app/src/main/res/layout/item_recycler_main.xml @@ -8,8 +8,9 @@ @@ -20,13 +21,20 @@ android:layout_height="@dimen/server_height" android:layout_gravity="center" android:gravity="center" - android:orientation="horizontal" - android:padding="5dp"> + android:orientation="horizontal"> + android:layout_height="match_parent" + android:layout_gravity="center" + android:orientation="vertical"> + + + + android:orientation="horizontal"> - + - + - + + + - + + diff --git a/V2rayNG/app/src/main/res/menu/menu_main.xml b/V2rayNG/app/src/main/res/menu/menu_main.xml index da591c3..ce66fb4 100644 --- a/V2rayNG/app/src/main/res/menu/menu_main.xml +++ b/V2rayNG/app/src/main/res/menu/menu_main.xml @@ -15,8 +15,12 @@ android:title="@string/menu_item_import_config_clipboard" app:showAsAction="never" /> + + + 删除配置
扫描二维码 从剪贴板导入 - 手动输入 + 手动输入[Vmess] + 手动输入[Shadowsocks] 从本地导入自定义配置 从URL导入自定义配置 扫描URL导入自定义配置 @@ -38,6 +39,10 @@ 伪装域名host(host/ws host/h2 host) path(ws path/h2 path) 底层传输安全 + 服务器地址 + 服务器端口 + 密码 + 加密方式 成功 失败 没有数据 @@ -83,6 +88,7 @@ 反馈 反馈改进或漏洞至 GitHub + 加入Telegram Group 版本 @@ -97,6 +103,10 @@ Logcat 复制 导出全部配置至剪贴板 + 订阅设置 + 备注 + 地址(url) + 更新订阅 启动服务 确定 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 91bf52b..64a6a1b 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -21,7 +21,8 @@ 刪除組態 自 QR 碼匯入組態 自剪貼簿匯入組態 - 手動鍵入 + 手動鍵入[Vmess] + 手動鍵入[Shadowsocks] 自本機匯入自訂組態 自網址匯入自訂組態 掃描網址匯入自訂組態 @@ -38,6 +39,10 @@ 要求 主機(host/ws host/h2 host) path(ws path/h2 path) 傳輸層安全性 + 伺服器位址 + 伺服器埠 + 密碼 + 加密方式 成功 失敗 無資料 @@ -84,6 +89,7 @@ 回饋 回饋提升或前往 GitHub 回報 Bug + 加入Telegram Group 版本 @@ -98,6 +104,10 @@ Logcat 複製 匯出全部配置至剪貼簿 + 訂閱設定 + 備註 + 位址(url) + 更新訂閱 啟動服務 確定 diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 7042e2f..11b656e 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -6,6 +6,16 @@ aes-128-gcm none + + aes-256-cfb + aes-128-cfb + chacha20 + chacha20-ietf + aes-256-gcm + aes-128-gcm + chacha20-poly1305 + chacha20-ietf-poly1305 + tcp diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 8505b53..33660af 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -21,7 +21,8 @@ Delete config Import config from QRcode Import config from Clipboard - Type manually + Type manually[Vmess] + Type manually[Shadowsocks] Import custom config from locally Import custom config from URL Import custom config scan URL @@ -38,6 +39,10 @@ request host(host/ws host/h2 host) path(ws path/h2 path) tls + address + port + password + security Success Failure There is nothing @@ -84,6 +89,7 @@ Feedback Feedback enhancements or bugs to GitHub + Join Telegram Group Version @@ -98,6 +104,10 @@ Logcat Copy Export all config to clipboard + subscription setting + remarks + url + Update subscription Start Service Confirm diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 083a509..6cf70ae 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -59,6 +59,10 @@ android:summary="@string/summary_pref_feedback" android:title="@string/title_pref_feedback" /> + + diff --git a/V2rayNG/app/src/main/res/xml/shortcuts.xml b/V2rayNG/app/src/main/res/xml/shortcuts.xml new file mode 100644 index 0000000..f68de4a --- /dev/null +++ b/V2rayNG/app/src/main/res/xml/shortcuts.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file From 697dc453a322744662893c98cee8f4cf65d66e4e Mon Sep 17 00:00:00 2001 From: 2dust Date: Mon, 6 Aug 2018 08:34:45 +0800 Subject: [PATCH 26/49] update --- V2rayNG/app/build.gradle | 69 ++++++------ V2rayNG/app/src/main/AndroidManifest.xml | 20 ++-- V2rayNG/app/src/main/assets/v2ray_config.json | 14 +++ .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 11 ++ .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 7 +- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 9 +- .../com/v2ray/ang/ui/ScScannerActivity.kt | 2 +- .../com/v2ray/ang/ui/ScSwitchActivity.kt | 104 ++++++++++++++++++ .../com/v2ray/ang/ui/SettingsActivity.kt | 8 ++ .../com/v2ray/ang/util/AngConfigManager.kt | 12 +- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 26 ++++- .../drawable/ic_qu_settings_black_24dp.xml | 17 +-- .../res/drawable/ic_qu_switch_black_24dp.xml | 18 +++ .../res/drawable/ic_shortcut_background.xml | 7 ++ .../res/drawable/ic_start_connected_black.xml | 13 +++ .../app/src/main/res/layout/activity_none.xml | 6 + .../src/main/res/values-zh-rCN/strings.xml | 3 + .../src/main/res/values-zh-rTW/strings.xml | 3 + V2rayNG/app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/xml/pref_settings.xml | 6 + V2rayNG/app/src/main/res/xml/shortcuts.xml | 14 +++ 21 files changed, 307 insertions(+), 65 deletions(-) create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/ScSwitchActivity.kt create mode 100644 V2rayNG/app/src/main/res/drawable/ic_qu_switch_black_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_shortcut_background.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_none.xml diff --git a/V2rayNG/app/build.gradle b/V2rayNG/app/build.gradle index 18a4775..f7ff19b 100644 --- a/V2rayNG/app/build.gradle +++ b/V2rayNG/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.v2ray.ang" minSdkVersion 17 targetSdkVersion Integer.parseInt("$targetSdkVer") - versionCode 55 - versionName "0.2.6" + versionCode 105 + versionName "0.5.3" } signingConfigs { @@ -73,44 +73,46 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile project(':libv2ray') - compile project(':dpreference') - compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support.constraint:constraint-layout:1.1.2' + testImplementation 'junit:junit:4.12' + implementation project(':libv2ray') + implementation project(':dpreference') + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" // Android support library - compile "com.android.support:support-v4:$supportLibVersion" - compile "com.android.support:appcompat-v7:$supportLibVersion" - compile "com.android.support:design:$supportLibVersion" - compile "com.android.support:cardview-v7:$supportLibVersion" - compile "com.android.support:preference-v7:$supportLibVersion" + 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" // DSL - compile "org.jetbrains.anko:anko-sdk15:$ankoVersion" - compile "org.jetbrains.anko:anko-support-v4:$ankoVersion" - compile "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion" - compile "org.jetbrains.anko:anko-design:$ankoVersion" - - compile 'com.google.code.gson:gson:2.7' - compile 'com.github.pwittchen:reactivenetwork:0.12.2' - compile 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar' - compile 'de.psdev.licensesdialog:licensesdialog:1.8.1' - compile 'com.dinuscxj:recycleritemdecoration:1.0.0' - compile 'io.reactivex:rxkotlin:0.60.0' - compile 'com.orhanobut:logger:1.15' - - // LeakCanary - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' - releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' - testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' + 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.2' + 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' + +// // LeakCanary +// debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' +// releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' +// testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' // Firebase - compile "com.google.firebase:firebase-core:$firebaseVersion" - compile "com.google.firebase:firebase-crash:$firebaseVersion" + implementation "com.google.firebase:firebase-core:$firebaseVersion" + implementation "com.google.firebase:firebase-crash:$firebaseVersion" - compile 'me.dm7.barcodescanner:core:1.9.3' - compile 'me.dm7.barcodescanner:zxing:1.9.3' + implementation 'me.dm7.barcodescanner:core:1.9.8' + implementation 'me.dm7.barcodescanner:zxing:1.9.8' + implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar' } buildscript { @@ -121,4 +123,3 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion" } } - diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index d01fb8e..2504423 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -61,7 +61,7 @@ android:name=".ui.SubSettingActivity" android:windowSoftInputMode="stateUnchanged" /> - + - - + + + + - - - - - + + + + + ?, var outbound: OutboundBean, val outboundDetour: List, val dns: DnsBean, @@ -91,6 +92,16 @@ data class V2rayConfig(val port: Int, data class MuxBean(var enabled: Boolean) + data class InboundDetourBean( + var port: Int, + val listen: String, + val protocol: String, + val settings: InSettingsBean, + val domainOverride: List) { + + data class InSettingsBean(val auth: String, + val udp: Boolean) + } data class OutboundDetourBean(val protocol: String, var settings: OutboundDetourSettingsBean, diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 91ef89f..63b61b4 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -100,9 +100,10 @@ class MainActivity : BaseActivity() { override fun onStop() { super.onStop() -// unbindService(mConnection) - unregisterReceiver(mMsgReceive) - mMsgReceive = null + if (mMsgReceive != null) { + unregisterReceiver(mMsgReceive) + mMsgReceive = null + } } public override fun onResume() { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index fb7a822..113b1d1 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -186,8 +186,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter = 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 +// } + } + } + } + +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 94b0ad4..244d132 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -26,6 +26,7 @@ class SettingsActivity : BaseActivity() { const val PREF_PER_APP_PROXY = "pref_per_app_proxy" const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_REMOTE_DNS = "pref_remote_dns" + const val PREF_LANCONN_PORT = "pref_lanconn_port" // const val PREF_SPEEDUP_DOMAIN = "pref_speedup_domain" const val PREF_ROUTING_MODE = "pref_routing_mode" @@ -49,6 +50,7 @@ class SettingsActivity : BaseActivity() { val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference } // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } + val lanconnPort by lazy { findPreference(PREF_LANCONN_PORT) as EditTextPreference } val routing: Preference by lazy { findPreference(PREF_ROUTING) } val donate: Preference by lazy { findPreference(PREF_DONATE) } @@ -97,6 +99,11 @@ class SettingsActivity : BaseActivity() { true } + lanconnPort.setOnPreferenceChangeListener { preference, any -> + lanconnPort.summary = any as String + true + } + version.summary = "${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})" } @@ -105,6 +112,7 @@ class SettingsActivity : BaseActivity() { perAppProxy.isChecked = defaultSharedPreferences.getBoolean(PREF_PER_APP_PROXY, false) remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "") + lanconnPort.summary = defaultSharedPreferences.getString(PREF_LANCONN_PORT, "") defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this) } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 6401a52..f43efbf 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -612,17 +612,17 @@ object AngConfigManager { return 0 } - fun importBatchConfig(server: String?, subid: String): Int { + fun importBatchConfig(servers: String?, subid: String): Int { try { - if (server == null) { + if (servers == null) { return 0 } removeServerViaSubid(subid) - var servers = server - if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) { - servers = server.replace("\n", "") - } +// var servers = server +// if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) { +// servers = server.replace("\n", "") +// } var count = 0 servers.lines() diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index ece5eec..8fb66c5 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -76,7 +76,7 @@ object V2rayConfigUtil { }, "domainOverride": ["http", "tls"] }"""), - "inboundDetour" to JSONArray(), + //"inboundDetour" to JSONArray(), "#lib2ray" to lib2rayObj, "log" to JSONObject("""{ "loglevel": "warning" @@ -144,6 +144,8 @@ object V2rayConfigUtil { // return result // } + inbound(config, v2rayConfig, app) + //vmess协议服务器配置 outbound(config, v2rayConfig, app) @@ -198,6 +200,28 @@ object V2rayConfigUtil { } } + /** + * + */ + private fun inbound(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { + try { + val lanconnPort = app.defaultDPreference.getPrefString(SettingsActivity.PREF_LANCONN_PORT, "") + val port = Utils.parseInt(lanconnPort) + + if (port == 0) { + v2rayConfig.inboundDetour = null + } else { + if (v2rayConfig.inboundDetour!!.isNotEmpty()) { + v2rayConfig.inboundDetour!!.get(0).port = port + } + } + } catch (e: Exception) { + e.printStackTrace() + return false + } + return true + } + /** * vmess协议服务器配置 */ diff --git a/V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml index 9ff4872..01f193f 100644 --- a/V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml +++ b/V2rayNG/app/src/main/res/drawable/ic_qu_settings_black_24dp.xml @@ -1,12 +1,13 @@ - - + android:viewportHeight="1024" + android:viewportWidth="1024"> + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_qu_switch_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_qu_switch_black_24dp.xml new file mode 100644 index 0000000..a514921 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_qu_switch_black_24dp.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/drawable/ic_shortcut_background.xml b/V2rayNG/app/src/main/res/drawable/ic_shortcut_background.xml new file mode 100644 index 0000000..61d2d99 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_shortcut_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml b/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml new file mode 100644 index 0000000..9038531 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_none.xml b/V2rayNG/app/src/main/res/layout/activity_none.xml new file mode 100644 index 0000000..dcc9bf8 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_none.xml @@ -0,0 +1,6 @@ + + + diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index 743eb37..da377a5 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -83,6 +83,9 @@ 远程DNS 远程DNS + 局域网的连接端口(0=不允许) + 局域网的连接端口 + 加速域名解析 直连解析,可能存在问题,http/tls有效 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 64a6a1b..cd5110e 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -84,6 +84,9 @@ 遠端 DNS 遠端 DNS + 局域網的連接端口(0=不允許) + 局域網的連接端口 + 加快網域名稱解析速度 只對 HTTP/TLS 有效 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 33660af..151caa6 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -84,6 +84,9 @@ Remote DNS Remote DNS + LAN connection port(0=not allowed) + LAN connection port + Speedup domain name resolution Valid only for http/tls diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 6cf70ae..fa9262a 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -21,6 +21,12 @@ android:summary="@string/summary_pref_remote_dns" android:title="@string/title_pref_remote_dns" /> + + diff --git a/V2rayNG/app/src/main/res/xml/shortcuts.xml b/V2rayNG/app/src/main/res/xml/shortcuts.xml index f68de4a..3e5f365 100644 --- a/V2rayNG/app/src/main/res/xml/shortcuts.xml +++ b/V2rayNG/app/src/main/res/xml/shortcuts.xml @@ -1,5 +1,19 @@ + + + + + Date: Mon, 27 Aug 2018 16:25:27 +0800 Subject: [PATCH 27/49] update --- V2rayNG/app/src/main/AndroidManifest.xml | 5 +- .../com/v2ray/ang/util/QRCodeDecoder.java | 116 ++++++++ .../kotlin/com/v2ray/ang/dto/VpnBandwidth.kt | 44 +++ .../kotlin/com/v2ray/ang/extension/_Ext.kt | 39 +++ .../com/v2ray/ang/service/V2RayVpnService.kt | 70 ++++- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 10 +- .../com/v2ray/ang/ui/ScannerActivity.kt | 90 ++++++- .../com/v2ray/ang/ui/SettingsActivity.kt | 9 +- .../com/v2ray/ang/ui/SubEditActivity.kt | 138 ++++++++++ .../com/v2ray/ang/ui/SubSettingActivity.kt | 86 ++---- .../v2ray/ang/ui/SubSettingRecyclerAdapter.kt | 62 +++++ .../com/v2ray/ang/util/AngConfigManager.kt | 38 +++ .../src/main/res/drawable/ic_image_photo.xml | 9 + .../src/main/res/layout/activity_sub_edit.xml | 86 ++++++ .../main/res/layout/activity_sub_setting.xml | 250 +----------------- .../main/res/layout/item_recycler_main.xml | 93 +++++-- .../res/layout/item_recycler_sub_setting.xml | 97 +++++++ .../src/main/res/menu/action_sub_setting.xml | 19 ++ .../app/src/main/res/menu/menu_scanner.xml | 9 + .../src/main/res/values-zh-rCN/strings.xml | 5 + .../src/main/res/values-zh-rTW/strings.xml | 5 + V2rayNG/app/src/main/res/values/strings.xml | 5 + .../app/src/main/res/xml/pref_settings.xml | 5 + 23 files changed, 941 insertions(+), 349 deletions(-) create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.java create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VpnBandwidth.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubEditActivity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt create mode 100644 V2rayNG/app/src/main/res/drawable/ic_image_photo.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_sub_edit.xml create mode 100644 V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml create mode 100644 V2rayNG/app/src/main/res/menu/action_sub_setting.xml create mode 100644 V2rayNG/app/src/main/res/menu/menu_scanner.xml diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index 2504423..8d74604 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -57,9 +57,8 @@ - + + diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.java new file mode 100644 index 0000000..1a16ac3 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/QRCodeDecoder.java @@ -0,0 +1,116 @@ +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 HINTS = new EnumMap<>(DecodeHintType.class); + + static { + List 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; + } + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VpnBandwidth.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VpnBandwidth.kt new file mode 100644 index 0000000..8034385 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/VpnBandwidth.kt @@ -0,0 +1,44 @@ +package com.v2ray.ang.dto + +import android.os.Parcel +import android.os.Parcelable +import android.util.Base64 + + +data class VpnBandwidth(val rxByte: Long = 0, val txByte: Long = 0) : Parcelable { + companion object { + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): VpnBandwidth = VpnBandwidth(source) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } + + constructor(source: Parcel) : this(source.readLong(), source.readLong()) + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel?, flags: Int) { + dest?.writeLong(rxByte) + dest?.writeLong(txByte) + } + + operator infix fun minus(other: VpnBandwidth) = VpnBandwidth( + rxByte - other.rxByte, + txByte - other.txByte + ) + + operator infix fun plus(other: VpnBandwidth) = VpnBandwidth( + rxByte + other.rxByte, + txByte + other.txByte + ) + + fun serializeString(): String { + val parcel = Parcel.obtain() + parcel.setDataPosition(0) + writeToParcel(parcel, 0) + val ret = Base64.encodeToString(parcel.marshall(), Base64.NO_WRAP) + parcel.recycle() + return ret + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt index 4be0cbc..dd93dae 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt @@ -18,3 +18,42 @@ val Context.defaultDPreference: DPreference fun JSONObject.putOpt(pair: Pair) = putOpt(pair.first, pair.second)!! fun JSONObject.putOpt(pairs: Map) = 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 "∞" +} + +private fun Float.toShortString(): String { + val s = toString() + if (s.length <= 4) + return s + return s.substring(0, 4).removeSuffix(".") +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 163223f..083d80e 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -16,7 +16,9 @@ import android.support.annotation.RequiresApi import android.support.v4.app.NotificationCompat import com.v2ray.ang.AppConfig import com.v2ray.ang.R +import com.v2ray.ang.dto.VpnBandwidth 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 @@ -26,7 +28,11 @@ import com.v2ray.ang.util.Utils import libv2ray.Libv2ray import libv2ray.V2RayCallbacks import libv2ray.V2RayVPNServiceSupportsSet +import org.jetbrains.anko.sp +import rx.Observable +import rx.Subscription import java.io.FileDescriptor +import java.io.FileInputStream import java.io.PrintWriter import java.lang.ref.SoftReference @@ -54,6 +60,10 @@ class V2RayVpnService : VpnService() { private lateinit var mInterface: ParcelFileDescriptor val fd: Int get() = mInterface.fd private var currentTimeMillis: Long = 0 + private var mBuilder: NotificationCompat.Builder? = null + private var mSubscription: Subscription? = null + private var lastVpnBandwidth: VpnBandwidth? = null + private var mNotificationManager: NotificationManager? = null override fun onCreate() { super.onCreate() @@ -123,6 +133,20 @@ class V2RayVpnService : VpnService() { mInterface = builder.establish() //Logger.d("VPNService", "New interface: " + parameters) //Logger.d(Libv2ray.checkVersionX()) + + + if (defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) { + mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS) + .subscribe { + vpnBandwidth?.let { + lastVpnBandwidth?.let { last -> + val speed = it - last + updateNotification("${(speed.txByte / 3).toSpeedString()} ↑ ${(speed.rxByte / 3).toSpeedString()} ↓") + } + lastVpnBandwidth = it + } + } + } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -265,7 +289,7 @@ class V2RayVpnService : VpnService() { "" } - val notification = NotificationCompat.Builder(applicationContext, channelId) + mBuilder = NotificationCompat.Builder(applicationContext, channelId) .setSmallIcon(R.drawable.ic_v) .setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) .setContentText(getString(R.string.notification_action_more)) @@ -274,9 +298,11 @@ class V2RayVpnService : VpnService() { .addAction(R.drawable.ic_close_grey_800_24dp, getString(R.string.notification_action_stop_v2ray), stopV2RayPendingIntent) - .build() + //.build() - startForeground(NOTIFICATION_ID, notification) + mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使 + + startForeground(NOTIFICATION_ID, mBuilder?.build()) // if (netWorkStateReceiver == null) { // netWorkStateReceiver = NetWorkStateReceiver() @@ -293,15 +319,49 @@ class V2RayVpnService : VpnService() { chan.lightColor = Color.DKGRAY chan.importance = NotificationManager.IMPORTANCE_NONE chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE - val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - service.createNotificationChannel(chan) + 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?.setContentText(contentText) + getNotificationManager().notify(NOTIFICATION_ID, mBuilder?.build()) + } } + private fun getNotificationManager(): NotificationManager { + if (mNotificationManager == null) { + mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + } + return mNotificationManager!! + } + + private val vpnBandwidth: VpnBandwidth? + get() = FileInputStream("/proc/net/dev").bufferedReader().use { + val prefix = "tun0:" + while (true) { + val line = it.readLine().trim() + if (line.startsWith(prefix)) { + val numbers = line.substring(prefix.length).split(' ') + .filter(String::isNotEmpty) + .map(String::toLong) + if (numbers.size > 10) + return VpnBandwidth(numbers[0], numbers[8]) + break + } + } + return null + } + + private inner class V2RayCallback : V2RayCallbacks, V2RayVPNServiceSupportsSet { override fun shutdown() = 0L diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 113b1d1..962d84c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -67,13 +67,16 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter { + RxPermissions(this) + .request(Manifest.permission.READ_EXTERNAL_STORAGE) + .subscribe { + if (it) { + try { + showFileChooser() + } catch (e: Exception) { + e.printStackTrace() + } + } else + toast(R.string.toast_permission_denied) + } + true + } + else -> super.onOptionsItemSelected(item) + } + + private fun showFileChooser() { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "image/*" + intent.addCategory(Intent.CATEGORY_OPENABLE) + //intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + + try { + startActivityForResult( + Intent.createChooser(intent, getString(R.string.title_file_chooser)), + REQUEST_FILE_CHOOSER) + } catch (ex: android.content.ActivityNotFoundException) { + toast(R.string.toast_require_file_manager) + } + } + + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + REQUEST_FILE_CHOOSER -> + if (resultCode == RESULT_OK) { + try { + val uri = data!!.data + val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)) + val text = QRCodeDecoder.syncDecodeQRCode(bitmap) + finished(text) + } catch (e: Exception) { + e.printStackTrace() + toast(e.message.toString()) + } + } + } } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 244d132..2d1cb5d 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -18,6 +18,7 @@ import libv2ray.Libv2ray import org.jetbrains.anko.act import org.jetbrains.anko.defaultSharedPreferences import org.jetbrains.anko.startActivity +import org.jetbrains.anko.toast class SettingsActivity : BaseActivity() { companion object { @@ -25,6 +26,7 @@ class SettingsActivity : BaseActivity() { // const val PREF_START_ON_BOOT = "pref_start_on_boot" const val PREF_PER_APP_PROXY = "pref_per_app_proxy" const val PREF_MUX_ENABLED = "pref_mux_enabled" + const val PREF_SPEED_ENABLED = "pref_speed_enabled" const val PREF_REMOTE_DNS = "pref_remote_dns" const val PREF_LANCONN_PORT = "pref_lanconn_port" // const val PREF_SPEEDUP_DOMAIN = "pref_speedup_domain" @@ -85,7 +87,12 @@ class SettingsActivity : BaseActivity() { tgGroup.onClick { // Utils.openUri(activity, "https://t.me/v2rayN") val intent = Intent(Intent.ACTION_VIEW, Uri.parse("tg:resolve?domain=v2rayN")) - startActivity(intent) + try { + startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + toast(R.string.toast_tg_app_not_found) + } } perAppProxy.setOnPreferenceClickListener { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubEditActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubEditActivity.kt new file mode 100644 index 0000000..d8cb7d2 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubEditActivity.kt @@ -0,0 +1,138 @@ +package com.v2ray.ang.ui + +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import com.v2ray.ang.R +import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.util.AngConfigManager +import com.v2ray.ang.util.Utils +import kotlinx.android.synthetic.main.activity_sub_edit.* +import org.jetbrains.anko.* + + +class SubEditActivity : BaseActivity() { + + var del_config: MenuItem? = null + var save_config: MenuItem? = null + + private lateinit var configs: AngConfig + private var edit_index: Int = -1 //当前编辑的 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_sub_edit) + + configs = AngConfigManager.configs + edit_index = intent.getIntExtra("position", -1) + + title = getString(R.string.title_sub_setting) + + if (edit_index >= 0) { + bindingServer(configs.subItem[edit_index]) + } else { + clearServer() + } + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } + + /** + * bingding seleced server config + */ + fun bindingServer(subItem: AngConfig.SubItemBean): Boolean { + et_remarks.text = Utils.getEditable(subItem.remarks) + et_url.text = Utils.getEditable(subItem.url) + + return true + } + + /** + * clear or init server config + */ + fun clearServer(): Boolean { + et_remarks.text = null + et_url.text = null + + return true + } + + /** + * save server config + */ + fun saveServer(): Boolean { + val subItem: AngConfig.SubItemBean + if (edit_index >= 0) { + subItem = configs.subItem[edit_index] + } else { + subItem = AngConfig.SubItemBean() + } + + subItem.remarks = et_remarks.text.toString() + subItem.url = et_url.text.toString() + + if (TextUtils.isEmpty(subItem.remarks)) { + toast(R.string.sub_setting_remarks) + return false + } + if (TextUtils.isEmpty(subItem.url)) { + toast(R.string.sub_setting_url) + return false + } + + if (AngConfigManager.addSubItem(subItem, edit_index) == 0) { + toast(R.string.toast_success) + finish() + return true + } else { + toast(R.string.toast_failure) + return false + } + } + + /** + * save server config + */ + fun deleteServer(): Boolean { + if (edit_index >= 0) { + alert(R.string.del_config_comfirm) { + positiveButton(android.R.string.ok) { + if (AngConfigManager.removeSubItem(edit_index) == 0) { + toast(R.string.toast_success) + finish() + } else { + toast(R.string.toast_failure) + } + } + show() + } + } else { + } + return true + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.action_server, menu) + del_config = menu?.findItem(R.id.del_config) + save_config = menu?.findItem(R.id.save_config) + + if (edit_index >= 0) { + } else { + del_config?.isVisible = false + } + + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.del_config -> { + deleteServer() + true + } + R.id.save_config -> { + saveServer() + true + } + else -> super.onOptionsItemSelected(item) + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt index 3b50209..82179b3 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt @@ -1,98 +1,50 @@ package com.v2ray.ang.ui -import android.os.Bundle -import android.text.TextUtils +import android.support.v7.widget.LinearLayoutManager import android.view.Menu import android.view.MenuItem import com.v2ray.ang.R -import com.v2ray.ang.dto.AngConfig -import com.v2ray.ang.util.AngConfigManager -import com.v2ray.ang.util.Utils import kotlinx.android.synthetic.main.activity_sub_setting.* -import org.jetbrains.anko.* - +import android.os.Bundle +import org.jetbrains.anko.startActivity class SubSettingActivity : BaseActivity() { - var del_config: MenuItem? = null - var save_config: MenuItem? = null - - private lateinit var configs: AngConfig + private val adapter by lazy { SubSettingRecyclerAdapter(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_sub_setting) - configs = AngConfigManager.configs - title = getString(R.string.title_sub_setting) - - bindingServer() + recycler_view.setHasFixedSize(true) + recycler_view.layoutManager = LinearLayoutManager(this) + recycler_view.adapter = adapter + title = getString(R.string.title_sub_setting) supportActionBar?.setDisplayHomeAsUpEnabled(true) } - /** - * bingding seleced server config - */ - fun bindingServer(): Boolean { - for (k in configs.subItem.count()..2) { - val subItem = AngConfig.SubItemBean() - configs.subItem.add(subItem) - } - - et_remarks.text = Utils.getEditable(configs.subItem[0].remarks) - et_url.text = Utils.getEditable(configs.subItem[0].url) - - et_remarks2.text = Utils.getEditable(configs.subItem[1].remarks) - et_url2.text = Utils.getEditable(configs.subItem[1].url) - - et_remarks3.text = Utils.getEditable(configs.subItem[2].remarks) - et_url3.text = Utils.getEditable(configs.subItem[2].url) - - return true - } - - /** - * save server config - */ - fun saveServer(): Boolean { - - configs.subItem[0].remarks = et_remarks.text.toString().trim() - configs.subItem[0].url = et_url.text.toString().trim() - - configs.subItem[1].remarks = et_remarks2.text.toString().trim() - configs.subItem[1].url = et_url2.text.toString().trim() - - configs.subItem[2].remarks = et_remarks3.text.toString().trim() - configs.subItem[2].url = et_url3.text.toString().trim() - - - if (AngConfigManager.saveSubItem(configs.subItem) == 0) { - toast(R.string.toast_success) - finish() - return true - } else { - toast(R.string.toast_failure) - return false - } + public override fun onResume() { + super.onResume() + adapter.updateConfigList() } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.action_server, menu) - del_config = menu?.findItem(R.id.del_config) - save_config = menu?.findItem(R.id.save_config) - - del_config?.isVisible = false + menuInflater.inflate(R.menu.action_sub_setting, menu) + menu?.findItem(R.id.del_config)?.isVisible = false + menu?.findItem(R.id.save_config)?.isVisible = false return super.onCreateOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { - R.id.save_config -> { - saveServer() + R.id.add_config -> { + startActivity("position" to -1) + adapter.updateConfigList() true } else -> super.onOptionsItemSelected(item) } + + } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt new file mode 100644 index 0000000..0ea5e67 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt @@ -0,0 +1,62 @@ +package com.v2ray.ang.ui + +import android.graphics.Color +import android.support.v7.widget.RecyclerView +import android.view.View +import android.view.ViewGroup +import com.v2ray.ang.R +import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.util.AngConfigManager +import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.* +import org.jetbrains.anko.* + +class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter() { + + private var mActivity: SubSettingActivity = activity + private lateinit var configs: AngConfig + + init { + updateConfigList() + } + + override fun getItemCount() = configs.subItem.count() + + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + if (holder is MainViewHolder) { + val remarks = configs.subItem[position].remarks + val url = configs.subItem[position].url + + holder.name.text = remarks + holder.url.text = url + holder.itemView.backgroundColor = Color.TRANSPARENT + + holder.layout_edit.setOnClickListener { + mActivity.startActivity("position" to position) + } + } else { + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { + return MainViewHolder(parent.context.layoutInflater + .inflate(R.layout.item_recycler_sub_setting, parent, false)) + } + + fun updateConfigList() { + configs = AngConfigManager.configs + notifyDataSetChanged() + } + + fun updateSelectedItem() { + notifyItemChanged(configs.index) + } + + open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) + + class MainViewHolder(itemView: View) : BaseViewHolder(itemView) { + val name = itemView.tv_name!! + val url = itemView.tv_url!! + val layout_edit = itemView.layout_edit!! + } + +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index f43efbf..57dfc72 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -673,4 +673,42 @@ object AngConfigManager { storeConfigFile() return 0 } + + fun addSubItem(subItem: AngConfig.SubItemBean, index: Int): Int { + try { + if (index >= 0) { + //edit + angConfig.subItem[index] = subItem + } else { + //add + angConfig.subItem.add(subItem) + } + + saveSubItem(angConfig.subItem) + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } + + /** + * + */ + fun removeSubItem(index: Int): Int { + try { + if (index < 0 || index > angConfig.subItem.count() - 1) { + return -1 + } + + //删除 + angConfig.subItem.removeAt(index) + + storeConfigFile() + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/drawable/ic_image_photo.xml b/V2rayNG/app/src/main/res/drawable/ic_image_photo.xml new file mode 100644 index 0000000..c701a63 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_image_photo.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml b/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml new file mode 100644 index 0000000..91f1735 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml b/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml index 746415b..7b0ba86 100644 --- a/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml +++ b/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml @@ -1,250 +1,24 @@ - + android:fitsSystemWindows="true" + tools:context=".ui.MainActivity"> - + android:layout_height="match_parent"> - + android:layout_height="match_parent" /> - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/item_recycler_main.xml b/V2rayNG/app/src/main/res/layout/item_recycler_main.xml index 76ef7ce..6076564 100644 --- a/V2rayNG/app/src/main/res/layout/item_recycler_main.xml +++ b/V2rayNG/app/src/main/res/layout/item_recycler_main.xml @@ -32,8 +32,8 @@ - + android:orientation="horizontal"> + + + + + android:orientation="vertical"> - - - + android:orientation="horizontal"> + + + + + + + + + + + + - - - + android:layout_height="wrap_content" + android:layout_gravity="right" + android:paddingEnd="5dp" + android:orientation="vertical"> + + - + diff --git a/V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml b/V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml new file mode 100644 index 0000000..30f93c1 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/action_sub_setting.xml b/V2rayNG/app/src/main/res/menu/action_sub_setting.xml new file mode 100644 index 0000000..11cb0c1 --- /dev/null +++ b/V2rayNG/app/src/main/res/menu/action_sub_setting.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_scanner.xml b/V2rayNG/app/src/main/res/menu/menu_scanner.xml new file mode 100644 index 0000000..b986bb5 --- /dev/null +++ b/V2rayNG/app/src/main/res/menu/menu_scanner.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index da377a5..fc5de25 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -39,6 +39,7 @@ 伪装域名host(host/ws host/h2 host) path(ws path/h2 path) 底层传输安全 + allowInsecure 服务器地址 服务器端口 密码 @@ -73,6 +74,9 @@ 开启Mux多路复用 开启可能会加速,关闭可能会减少断流 + 开启速度显示 + 在通知中显示当前速度 + 路由 自定义路由 @@ -92,6 +96,7 @@ 反馈 反馈改进或漏洞至 GitHub 加入Telegram Group + 未找到Telegram app 版本 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index cd5110e..ece6a6c 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -39,6 +39,7 @@ 要求 主機(host/ws host/h2 host) path(ws path/h2 path) 傳輸層安全性 + allowInsecure 伺服器位址 伺服器埠 密碼 @@ -74,6 +75,9 @@ 啟用 Mux 啟用或許會加快網路速度,切換或許會閃爍 + 開啟速度顯示 + 在通知中顯示當前速度 + 路由 自訂路由 @@ -93,6 +97,7 @@ 回饋 回饋提升或前往 GitHub 回報 Bug 加入Telegram Group + 未找到Telegram app 版本 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 151caa6..394bfe0 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ request host(host/ws host/h2 host) path(ws path/h2 path) tls + allowInsecure address port password @@ -74,6 +75,9 @@ Enable Mux Enable maybe speed up network and switch network maybe flash + Enable speed display + Display current speed in the notification + Routing Custom routing @@ -93,6 +97,7 @@ Feedback Feedback enhancements or bugs to GitHub Join Telegram Group + Telegram app not found Version diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index fa9262a..aae1e17 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -15,6 +15,11 @@ android:summary="@string/summary_pref_mux_enabled" android:title="@string/title_pref_mux_enabled" /> + + Date: Sat, 29 Sep 2018 11:17:41 +0800 Subject: [PATCH 28/49] update --- V2rayNG/app/src/main/assets/v2ray_config.json | 30 ++++++----- .../main/kotlin/com/v2ray/ang/AppConfig.kt | 4 +- .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 11 +++- .../kotlin/com/v2ray/ang/extension/_Ext.kt | 7 ++- .../com/v2ray/ang/service/V2RayVpnService.kt | 53 ++++++++++++++----- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 15 +++++- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 22 +++++--- .../main/kotlin/com/v2ray/ang/util/Utils.kt | 53 ++++++++++++++++++- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 10 +++- .../app/src/main/res/layout/activity_main.xml | 36 +++++++++++-- .../src/main/res/values-zh-rCN/strings.xml | 7 +++ .../src/main/res/values-zh-rTW/strings.xml | 7 +++ V2rayNG/app/src/main/res/values/arrays.xml | 2 + V2rayNG/app/src/main/res/values/dimens.xml | 1 + V2rayNG/app/src/main/res/values/strings.xml | 7 +++ 15 files changed, 219 insertions(+), 46 deletions(-) diff --git a/V2rayNG/app/src/main/assets/v2ray_config.json b/V2rayNG/app/src/main/assets/v2ray_config.json index 5660d31..92fd524 100644 --- a/V2rayNG/app/src/main/assets/v2ray_config.json +++ b/V2rayNG/app/src/main/assets/v2ray_config.json @@ -10,10 +10,13 @@ "auth": "noauth", "udp": true }, - "domainOverride": [ - "http", - "tls" - ] + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ] + } }, "inboundDetour": [{ "port": 18888, @@ -24,13 +27,16 @@ "udp": true, "ip": "0.0.0.0" }, - "domainOverride": [ - "http", - "tls" - ] + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ] + } }], "outbound": { - "tag": "agentout", + "tag": "proxyout", "protocol": "vmess", "settings": { "vnext": [ @@ -68,7 +74,7 @@ { "protocol": "freedom", "settings": {}, - "tag": "direct" + "tag": "directout" }, { "protocol": "blackhole", @@ -99,7 +105,7 @@ "ip": [ "0.0.0.0/8" ], - "outboundTag": "direct" + "outboundTag": "directout" }, { "type": "field", @@ -122,7 +128,7 @@ "fc00::/7", "fe80::/10" ], - "outboundTag": "direct" + "outboundTag": "directout" } ] } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt index c169b50..98a8dc9 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt @@ -24,8 +24,8 @@ object AppConfig { 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 = "agentout" - const val TAG_DIRECT = "direct" + const val TAG_AGENT = "proxyout" + const val TAG_DIRECT = "directout" const val TAG_BLOCKED = "blockout" const val MSG_REGISTER_CLIENT = 1 diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt index 810f3b0..8f44a51 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/V2rayConfig.kt @@ -15,12 +15,16 @@ data class V2rayConfig(val port: Int, data class InboundBean( val listen: String, + //val port: Int, val protocol: String, val settings: InSettingsBean, - val domainOverride: List) { + val sniffing: SniffingBean) { data class InSettingsBean(val auth: String, val udp: Boolean) + + data class SniffingBean(val enabled: Boolean, + val destOverride: List) } data class OutboundBean(val tag: String, @@ -97,10 +101,13 @@ data class V2rayConfig(val port: Int, val listen: String, val protocol: String, val settings: InSettingsBean, - val domainOverride: List) { + val sniffing: SniffingBean) { data class InSettingsBean(val auth: String, val udp: Boolean) + + data class SniffingBean(val enabled: Boolean, + val destOverride: List) } data class OutboundDetourBean(val protocol: String, diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt index dd93dae..fa028c4 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/extension/_Ext.kt @@ -1,9 +1,11 @@ package com.v2ray.ang.extension import android.content.Context +import android.os.Build import com.v2ray.ang.AngApplication import me.dozen.dpreference.DPreference import org.json.JSONObject +import java.net.URLConnection /** * Some extensions @@ -56,4 +58,7 @@ private fun Float.toShortString(): String { if (s.length <= 4) return s return s.substring(0, 4).removeSuffix(".") -} \ No newline at end of file +} + +val URLConnection.responseLength: Long + get() = if (Build.VERSION.SDK_INT >= 24) contentLengthLong else contentLength.toLong() diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 083d80e..81972e7 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -14,6 +14,7 @@ import android.net.VpnService import android.os.* import android.support.annotation.RequiresApi import android.support.v4.app.NotificationCompat +import android.util.Log import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.dto.VpnBandwidth @@ -250,8 +251,9 @@ class V2RayVpnService : VpnService() { // // val path2 = AssetsUtil.getAssetPath(this, "geosite.dat") // Libv2ray.setAssetsOverride("geosite.dat", path2) + Log.d("restartV2Ray", "restartV2Ray") - stopV2Ray(false) + //stopV2Ray(false) startV2ray(true) } catch (e: Exception) { } @@ -344,21 +346,46 @@ class V2RayVpnService : VpnService() { return mNotificationManager!! } +// private val vpnBandwidth: VpnBandwidth? +// get() = FileInputStream("/proc/net/dev").bufferedReader().use { +// val prefix = "tun0:" +// while (true) { +// val line = it.readLine().trim() +// if (line.startsWith(prefix)) { +// val numbers = line.substring(prefix.length).split(' ') +// .filter(String::isNotEmpty) +// .map(String::toLong) +// if (numbers.size > 10) +// return VpnBandwidth(numbers[0], numbers[8]) +// break +// } +// } +// return null +// } + private val vpnBandwidth: VpnBandwidth? - get() = FileInputStream("/proc/net/dev").bufferedReader().use { - val prefix = "tun0:" - while (true) { - val line = it.readLine().trim() - if (line.startsWith(prefix)) { - val numbers = line.substring(prefix.length).split(' ') - .filter(String::isNotEmpty) - .map(String::toLong) - if (numbers.size > 10) - return VpnBandwidth(numbers[0], numbers[8]) - break + get() { + try { + val netDev = FileInputStream("/proc/net/dev").bufferedReader() + var bandWidth: VpnBandwidth? = null + val prefix = "tun0:" + while (true) { + val line = netDev.readLine().trim() + if (line.startsWith(prefix)) { + val numbers = line.substring(prefix.length).split(' ') + .filter(String::isNotEmpty) + .map(String::toLong) + if (numbers.size > 10) + bandWidth = VpnBandwidth(numbers[0], numbers[8]) + break + } } + netDev.close() + return bandWidth + } catch (e: Exception) { + e.printStackTrace() + return null } - return null } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 63b61b4..1dec287 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -28,7 +28,7 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import java.util.concurrent.TimeUnit import com.v2ray.ang.helper.SimpleItemTouchHelperCallback - +import kotlinx.android.synthetic.main.activity_server3.* class MainActivity : BaseActivity() { companion object { @@ -69,6 +69,19 @@ class MainActivity : BaseActivity() { } } } + layout_test.setOnClickListener { + if (isRunning) { + tv_test_state.text = getString(R.string.connection_test_testing) + doAsync { + val result = Utils.testConnection(this@MainActivity) + uiThread { + tv_test_state.text = Utils.getEditable(result) + } + } + } else { + tv_test_state.text = getString(R.string.connection_test_fail) + } + } recycler_view.setHasFixedSize(true) recycler_view.layoutManager = LinearLayoutManager(this) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 962d84c..84c14b8 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -17,7 +17,10 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.item_qrcode.view.* import kotlinx.android.synthetic.main.item_recycler_main.view.* import org.jetbrains.anko.* +import rx.Observable +import rx.android.schedulers.AndroidSchedulers import java.util.* +import java.util.concurrent.TimeUnit class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter() , ItemTouchHelperAdapter { @@ -126,9 +129,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter - + android:layout_height="match_parent" + android:orientation="vertical"> + + + + + + + + + + + diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index fc5de25..4038b77 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -126,6 +126,13 @@ 扫描并替换 扫描并追加 + "检查网络连接" + "测试中…" + "连接成功:延时 %d 毫秒" + "失败:%s" + "无互联网连接" + "状态码无效(#%d)" + 二维码 导出至剪贴板 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index ece6a6c..3dc962c 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -127,6 +127,13 @@ 掃描並取代 掃描並附加 + "檢查連線能力" + "測試中……" + "成功: %d 毫秒延遲" + "偵測出網際網路連線失敗: %s" + "無法使用網際網路" + "錯誤碼: (#%d)" + QR 碼 匯出至剪貼簿 diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 11b656e..f9e72cb 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -31,6 +31,7 @@ utp wechat-video dtls + wireguard @@ -44,6 +45,7 @@ utp wechat-video dtls + wireguard diff --git a/V2rayNG/app/src/main/res/values/dimens.xml b/V2rayNG/app/src/main/res/values/dimens.xml index 62e38ab..19b4e98 100644 --- a/V2rayNG/app/src/main/res/values/dimens.xml +++ b/V2rayNG/app/src/main/res/values/dimens.xml @@ -6,4 +6,5 @@ 50dp 24dp 72dp + 60dp \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 394bfe0..43b0869 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -127,6 +127,13 @@ Scan and replace Scan and append + Check Connectivity + Testing… + Success: HTTPS handshake took %dms + Fail to detect internet connection: %s + Internet Unavailable + Error code: #%d + QRcode Export to clipboard From e701c28a1b09e2192e1e1b2d161dfa2952e94383 Mon Sep 17 00:00:00 2001 From: LI JIAHAO Date: Tue, 6 Nov 2018 06:50:06 +0800 Subject: [PATCH 29/49] :lipstick: change icon to v2ray --- V2rayNG/app/src/main/AndroidManifest.xml | 2 +- .../com/v2ray/ang/service/QSTileService.kt | 4 +-- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 4 +-- .../src/main/res/drawable/ic_fab_uncheck.png | Bin 1755 -> 0 bytes .../res/drawable/ic_qu_switch_black_24dp.xml | 2 +- .../src/main/res/drawable/ic_start_busy.xml | 13 ---------- .../main/res/drawable/ic_start_connected.xml | 13 ---------- .../res/drawable/ic_start_connected_black.xml | 13 ---------- .../src/main/res/drawable/ic_start_idle.xml | 24 ------------------ V2rayNG/app/src/main/res/drawable/ic_v.xml | 1 + .../res/drawable/ic_v_connected_black.xml | 10 ++++++++ .../app/src/main/res/drawable/ic_v_idle.xml | 21 +++++++++++++++ .../app/src/main/res/layout/activity_main.xml | 5 ++-- V2rayNG/app/src/main/res/values/styles.xml | 1 + 14 files changed, 42 insertions(+), 71 deletions(-) delete mode 100644 V2rayNG/app/src/main/res/drawable/ic_fab_uncheck.png delete mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_busy.xml delete mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_connected.xml delete mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml delete mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_idle.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_v_connected_black.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_v_idle.xml diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index 8d74604..a8e611a 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -83,7 +83,7 @@ diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt index c7994f3..cbb8242 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/QSTileService.kt @@ -26,11 +26,11 @@ 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_start_idle) + qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle) } else if (state == Tile.STATE_ACTIVE) { qsTile?.state = Tile.STATE_ACTIVE qsTile?.label = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG") - qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_start_connected) + qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v) } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 1dec287..f8c0a3d 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -43,10 +43,10 @@ class MainActivity : BaseActivity() { field = value adapter.changeable = !value if (value) { - fab.imageResource = R.drawable.ic_start_connected + fab.imageResource = R.drawable.ic_v hideCircle() } else { - fab.imageResource = R.drawable.ic_start_idle + fab.imageResource = R.drawable.ic_v_idle } } diff --git a/V2rayNG/app/src/main/res/drawable/ic_fab_uncheck.png b/V2rayNG/app/src/main/res/drawable/ic_fab_uncheck.png deleted file mode 100644 index 39a104135c420512f0de150b18ff63a4d94a53cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1755 zcmV<11|<23P)ar0 zeMyK$q7U^Up(3PtAZXCcUcbMb^EmEzhP}_Y?|XN_;@o@A+Iz44yKB$qcJymYDuIqR zpL7CkKp7Fz2Gj;**NJp>b?u1dmFT37j*iR1RP6|Y1aQvbax;1{x(-0&`aB?@>;Q!E z=+)?@=*|I@g#db}PXjU{#%x6s0Q6d)1C$am(N5|ozMKr8@B0*>jEIQ>dILBYL~r!! zP8u-7sO&6sTBMIj8`d5H&;z|5kVC|nu*L)E^ai`BrH7)Q>_PP&GOF+BzdSHR3)J0mCAOAc6t@9QTU=G5$>(M*W{R)6l(y!11(A_Qb5zK)A z`qeu?fH@MUu0l1J8Dv?ZG_zF!oKAIXb!m zK-x5mm0AG-W-q*$jNXqP?pTo|bHTX{{TkIqm-d!jWG;Z7XcSNd5fj$uOFpPWS3mL+ zx~~fvZ2vxdC%}!O)Rh6MLvrw2ZW=VyXfdd zYb9-3RUw!n4yAF;WE%B++v z<(|vsMRXsFv`Ho!{rZ#VpQQuB!JWudPA))CE#Rb0QXR!i41a0L9?19iQB5+7$misF zVpaxDeNc@N6Z-uoA7)Y!Eguv)1Gv15j>)Q+#q%ZPdt;WJ6`-0UCiM9&K3qX1ehY8l zhej-Iua+UR$@{6Sj8XyNkQRJ8$iEdGg}TmCv&xTquF2bDQY!F!1kO}GFGY)V%3*fu zf6gHP53?MM1=LW)$X9ik^KpDp)N%aLX{S2qyeAeWhtIe%T%(~ApoStQ^ieb7Y39C6 zu;PD*XJxGR!?KoAj?T_9WxEK%8NK!~7u_>6Ww{2Qkew!fx^_!2V!gtR(d8D`Zx&@~ zl9?$cAbmd`)gH}-=qZ^w6gLnQy>hbA%nCruxTnLwvSlRYy=RkYRwsQNnW+5>m|&IN zNvp3>pVMQq2AnxOOI-%oR4~ToZ|-S=_>WC`+hJa(mLmgD=X33GL~B!6#3tO}nunuX zV4IkJ&)N7D+V}uSeb+VUS*T{r;S1d^^{}-a z3ZPGIjb+z3ph$C+FWSI54b_OKYvPHgOCn6&=!}p-T*S4rs?g*y|aVJ(q9I& z1S+!Cu^u?iYdUWMMS%sdhD0Ju%~89HF0u$fIx2FKmH-qJU7BSp8WdT&9%fPN$pF&J z4NlUMfQ;zU(nq_ViYhIA^tzrYe{o|NfWB~|gK^`4Xg>4sV<`HnluBr|olD3|_0bWu zB1^!=qMANvZP!WOI>UDo9{?@D - - - - diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml b/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml deleted file mode 100644 index bc8c366..0000000 --- a/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml b/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml deleted file mode 100644 index 9038531..0000000 --- a/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml b/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml deleted file mode 100644 index ef9b99d..0000000 --- a/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/V2rayNG/app/src/main/res/drawable/ic_v.xml b/V2rayNG/app/src/main/res/drawable/ic_v.xml index 24088c0..cb4930c 100644 --- a/V2rayNG/app/src/main/res/drawable/ic_v.xml +++ b/V2rayNG/app/src/main/res/drawable/ic_v.xml @@ -3,6 +3,7 @@ android:height="24dp" android:viewportHeight="1024.0" android:viewportWidth="1024.0"> + diff --git a/V2rayNG/app/src/main/res/drawable/ic_v_connected_black.xml b/V2rayNG/app/src/main/res/drawable/ic_v_connected_black.xml new file mode 100644 index 0000000..0c216c1 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_v_connected_black.xml @@ -0,0 +1,10 @@ + + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_v_idle.xml b/V2rayNG/app/src/main/res/drawable/ic_v_idle.xml new file mode 100644 index 0000000..87f0cca --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_v_idle.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_main.xml b/V2rayNG/app/src/main/res/layout/activity_main.xml index b9a6ce2..ce3cb43 100644 --- a/V2rayNG/app/src/main/res/layout/activity_main.xml +++ b/V2rayNG/app/src/main/res/layout/activity_main.xml @@ -55,11 +55,12 @@ android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" android:layout_gravity="bottom|end" android:layout_margin="20dp" - android:src="@drawable/ic_fab_uncheck" + android:src="@drawable/ic_v_idle" + app:backgroundTint="@color/fab_orange_bright" app:layout_anchorGravity="bottom|right|end" /> diff --git a/V2rayNG/app/src/main/res/values/styles.xml b/V2rayNG/app/src/main/res/values/styles.xml index 7db6f05..61d4eae 100644 --- a/V2rayNG/app/src/main/res/values/styles.xml +++ b/V2rayNG/app/src/main/res/values/styles.xml @@ -6,6 +6,7 @@ + @color/fab_orange_light From a62abc2630224931f7416d29362defb8ffadbae0 Mon Sep 17 00:00:00 2001 From: 2dust Date: Thu, 8 Nov 2018 14:05:36 +0800 Subject: [PATCH 30/49] update --- .../com/v2ray/ang/service/V2RayVpnService.kt | 5 ++- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 16 ++++--- .../v2ray/ang/ui/RoutingSettingsFragment.kt | 12 +++--- .../com/v2ray/ang/ui/SettingsActivity.kt | 26 ++++++------ .../main/kotlin/com/v2ray/ang/util/Utils.kt | 2 +- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 40 +++++++++--------- .../src/main/res/drawable/ic_fab_uncheck.png | Bin 0 -> 1755 bytes .../src/main/res/drawable/ic_start_busy.xml | 13 ++++++ .../main/res/drawable/ic_start_connected.xml | 13 ++++++ .../res/drawable/ic_start_connected_black.xml | 13 ++++++ .../src/main/res/drawable/ic_start_idle.xml | 24 +++++++++++ .../app/src/main/res/layout/activity_main.xml | 4 +- .../src/main/res/values-zh-rCN/strings.xml | 4 +- .../src/main/res/values-zh-rTW/strings.xml | 4 +- V2rayNG/app/src/main/res/values/arrays.xml | 4 +- V2rayNG/app/src/main/res/values/strings.xml | 4 +- V2rayNG/app/src/main/res/values/styles.xml | 2 +- .../app/src/main/res/xml/pref_settings.xml | 14 +++--- 18 files changed, 137 insertions(+), 63 deletions(-) create mode 100644 V2rayNG/app/src/main/res/drawable/ic_fab_uncheck.png create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_busy.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_connected.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_start_idle.xml diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 81972e7..0ab5b55 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -296,13 +296,16 @@ class V2RayVpnService : VpnService() { .setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) .setContentText(getString(R.string.notification_action_more)) .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) //取消震动,铃声其他都不好使 + //mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使 startForeground(NOTIFICATION_ID, mBuilder?.build()) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index f8c0a3d..42768cc 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -44,9 +44,11 @@ class MainActivity : BaseActivity() { adapter.changeable = !value if (value) { fab.imageResource = R.drawable.ic_v + tv_test_state.text = getString(R.string.connection_connected) hideCircle() } else { fab.imageResource = R.drawable.ic_v_idle + tv_test_state.text = getString(R.string.connection_not_connected) } } @@ -79,7 +81,7 @@ class MainActivity : BaseActivity() { } } } else { - tv_test_state.text = getString(R.string.connection_test_fail) +// tv_test_state.text = getString(R.string.connection_test_fail) } } @@ -223,11 +225,11 @@ class MainActivity : BaseActivity() { * import config from qrcode */ fun importQRcode(requestCode: Int): Boolean { - try { - startActivityForResult(Intent("com.google.zxing.client.android.SCAN") - .addCategory(Intent.CATEGORY_DEFAULT) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) - } catch (e: Exception) { +// 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 { @@ -236,7 +238,7 @@ class MainActivity : BaseActivity() { else toast(R.string.toast_permission_denied) } - } +// } return true } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt index c6ed6d5..6966e62 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt @@ -78,11 +78,11 @@ class RoutingSettingsFragment : Fragment() { } fun scanQRcode(requestCode: Int): Boolean { - try { - startActivityForResult(Intent("com.google.zxing.client.android.SCAN") - .addCategory(Intent.CATEGORY_DEFAULT) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) - } catch (e: Exception) { +// try { +// startActivityForResult(Intent("com.google.zxing.client.android.SCAN") +// .addCategory(Intent.CATEGORY_DEFAULT) +// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) +// } catch (e: Exception) { RxPermissions(activity!!) .request(Manifest.permission.CAMERA) .subscribe { @@ -91,7 +91,7 @@ class RoutingSettingsFragment : Fragment() { else activity?.toast(R.string.toast_permission_denied) } - } +// } return true } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 2d1cb5d..4f8a9bb 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -25,7 +25,7 @@ class SettingsActivity : BaseActivity() { // const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland" // const val PREF_START_ON_BOOT = "pref_start_on_boot" const val PREF_PER_APP_PROXY = "pref_per_app_proxy" - const val PREF_MUX_ENABLED = "pref_mux_enabled" +// const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_SPEED_ENABLED = "pref_speed_enabled" const val PREF_REMOTE_DNS = "pref_remote_dns" const val PREF_LANCONN_PORT = "pref_lanconn_port" @@ -36,7 +36,7 @@ class SettingsActivity : BaseActivity() { const val PREF_DONATE = "pref_donate" // const val PREF_LICENSES = "pref_licenses" const val PREF_FEEDBACK = "pref_feedback" - const val PREF_TG_GROUP = "pref_tg_group" +// const val PREF_TG_GROUP = "pref_tg_group" const val PREF_VERSION = "pref_version" // const val PREF_AUTO_RESTART = "pref_auto_restart" } @@ -58,7 +58,7 @@ class SettingsActivity : BaseActivity() { val donate: Preference by lazy { findPreference(PREF_DONATE) } // val licenses: Preference by lazy { findPreference(PREF_LICENSES) } val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } - val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) } +// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) } val version: Preference by lazy { findPreference(PREF_VERSION) } override fun onCreate(savedInstanceState: Bundle?) { @@ -84,16 +84,16 @@ class SettingsActivity : BaseActivity() { feedback.onClick { Utils.openUri(activity, "https://github.com/2dust/v2rayNG/issues") } - tgGroup.onClick { - // Utils.openUri(activity, "https://t.me/v2rayN") - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("tg:resolve?domain=v2rayN")) - try { - startActivity(intent) - } catch (e: Exception) { - e.printStackTrace() - toast(R.string.toast_tg_app_not_found) - } - } +// tgGroup.onClick { +// // Utils.openUri(activity, "https://t.me/v2rayN") +// val intent = Intent(Intent.ACTION_VIEW, Uri.parse("tg:resolve?domain=v2rayN")) +// try { +// startActivity(intent) +// } catch (e: Exception) { +// e.printStackTrace() +// toast(R.string.toast_tg_app_not_found) +// } +// } perAppProxy.setOnPreferenceClickListener { startActivity() diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 235134f..5f81e56 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -341,7 +341,7 @@ object Utils { // Log.d("testConnection", "222222222222") conn = url.openConnection(Proxy(Proxy.Type.SOCKS, - InetSocketAddress("127.0.0.1", 10808))) + InetSocketAddress("localhost", 10808))) as HttpURLConnection // Log.d("testConnection", "333333333333") diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 1b891c1..1c16bd9 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -164,7 +164,7 @@ object V2rayConfigUtil { //增加lib2ray val finalConfig = addLib2ray(v2rayConfig, app) - Log.d("config", finalConfig) + //Log.d("config", finalConfig) result.status = true result.content = finalConfig @@ -248,7 +248,7 @@ object V2rayConfigUtil { user?.security = vmess.security //Mux - val muxEnabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) + val muxEnabled = false//app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) v2rayConfig.outbound.mux.enabled = muxEnabled //远程服务器底层传输配置 @@ -409,27 +409,27 @@ object V2rayConfigUtil { when (routingMode) { "0" -> { } - "1" -> { + "1", "2" -> { routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) - routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) - routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) +// routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) +// routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) - routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) - } - "2" -> { - routingGeo("", "custom:reject", AppConfig.TAG_BLOCKED, v2rayConfig) - routingGeo("", "surge:reject", AppConfig.TAG_BLOCKED, v2rayConfig) - - routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) - routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) - routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) - routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) - routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) - - routingGeo("", "custom:proxy", AppConfig.TAG_AGENT, v2rayConfig) - routingGeo("", "surge:proxy", AppConfig.TAG_AGENT, v2rayConfig) - routingGeo("domain", "gfwlist:proxy", AppConfig.TAG_AGENT, v2rayConfig) +// routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) } +// "2" -> { +// routingGeo("", "custom:reject", AppConfig.TAG_BLOCKED, v2rayConfig) +// routingGeo("", "surge:reject", AppConfig.TAG_BLOCKED, v2rayConfig) +// +// routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) +// routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) +// routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) +// routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) +// routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) +// +// routingGeo("", "custom:proxy", AppConfig.TAG_AGENT, v2rayConfig) +// routingGeo("", "surge:proxy", AppConfig.TAG_AGENT, v2rayConfig) +// routingGeo("domain", "gfwlist:proxy", AppConfig.TAG_AGENT, v2rayConfig) +// } } } catch (e: Exception) { e.printStackTrace() diff --git a/V2rayNG/app/src/main/res/drawable/ic_fab_uncheck.png b/V2rayNG/app/src/main/res/drawable/ic_fab_uncheck.png new file mode 100644 index 0000000000000000000000000000000000000000..39a104135c420512f0de150b18ff63a4d94a53cf GIT binary patch literal 1755 zcmV<11|<23P)ar0 zeMyK$q7U^Up(3PtAZXCcUcbMb^EmEzhP}_Y?|XN_;@o@A+Iz44yKB$qcJymYDuIqR zpL7CkKp7Fz2Gj;**NJp>b?u1dmFT37j*iR1RP6|Y1aQvbax;1{x(-0&`aB?@>;Q!E z=+)?@=*|I@g#db}PXjU{#%x6s0Q6d)1C$am(N5|ozMKr8@B0*>jEIQ>dILBYL~r!! zP8u-7sO&6sTBMIj8`d5H&;z|5kVC|nu*L)E^ai`BrH7)Q>_PP&GOF+BzdSHR3)J0mCAOAc6t@9QTU=G5$>(M*W{R)6l(y!11(A_Qb5zK)A z`qeu?fH@MUu0l1J8Dv?ZG_zF!oKAIXb!m zK-x5mm0AG-W-q*$jNXqP?pTo|bHTX{{TkIqm-d!jWG;Z7XcSNd5fj$uOFpPWS3mL+ zx~~fvZ2vxdC%}!O)Rh6MLvrw2ZW=VyXfdd zYb9-3RUw!n4yAF;WE%B++v z<(|vsMRXsFv`Ho!{rZ#VpQQuB!JWudPA))CE#Rb0QXR!i41a0L9?19iQB5+7$misF zVpaxDeNc@N6Z-uoA7)Y!Eguv)1Gv15j>)Q+#q%ZPdt;WJ6`-0UCiM9&K3qX1ehY8l zhej-Iua+UR$@{6Sj8XyNkQRJ8$iEdGg}TmCv&xTquF2bDQY!F!1kO}GFGY)V%3*fu zf6gHP53?MM1=LW)$X9ik^KpDp)N%aLX{S2qyeAeWhtIe%T%(~ApoStQ^ieb7Y39C6 zu;PD*XJxGR!?KoAj?T_9WxEK%8NK!~7u_>6Ww{2Qkew!fx^_!2V!gtR(d8D`Zx&@~ zl9?$cAbmd`)gH}-=qZ^w6gLnQy>hbA%nCruxTnLwvSlRYy=RkYRwsQNnW+5>m|&IN zNvp3>pVMQq2AnxOOI-%oR4~ToZ|-S=_>WC`+hJa(mLmgD=X33GL~B!6#3tO}nunuX zV4IkJ&)N7D+V}uSeb+VUS*T{r;S1d^^{}-a z3ZPGIjb+z3ph$C+FWSI54b_OKYvPHgOCn6&=!}p-T*S4rs?g*y|aVJ(q9I& z1S+!Cu^u?iYdUWMMS%sdhD0Ju%~89HF0u$fIx2FKmH-qJU7BSp8WdT&9%fPN$pF&J z4NlUMfQ;zU(nq_ViYhIA^tzrYe{o|NfWB~|gK^`4Xg>4sV<`HnluBr|olD3|_0bWu zB1^!=qMANvZP!WOI>UDo9{?@D + + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml b/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml new file mode 100644 index 0000000..bc8c366 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_start_connected.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml b/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml new file mode 100644 index 0000000..9038531 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_start_connected_black.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml b/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml new file mode 100644 index 0000000..ef9b99d --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_start_idle.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_main.xml b/V2rayNG/app/src/main/res/layout/activity_main.xml index ce3cb43..0a090be 100644 --- a/V2rayNG/app/src/main/res/layout/activity_main.xml +++ b/V2rayNG/app/src/main/res/layout/activity_main.xml @@ -34,7 +34,8 @@ android:id="@+id/tv_test_state" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:lines="1" + android:minLines="1" + android:maxLines="2" android:paddingStart="16dp" android:text="@string/connection_test_pending" android:textAppearance="@style/TextAppearance.AppCompat.Small" @@ -60,7 +61,6 @@ android:layout_gravity="bottom|end" android:layout_margin="20dp" android:src="@drawable/ic_v_idle" - app:backgroundTint="@color/fab_orange_bright" app:layout_anchorGravity="bottom|right|end" /> diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index 4038b77..0d67f8d 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -132,6 +132,8 @@ "失败:%s" "无互联网连接" "状态码无效(#%d)" + "已连接,点击测试连接" + "未连接" 二维码 @@ -147,7 +149,7 @@ 全局 绕过大陆地址 - Geo全家桶 + diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 3dc962c..1bc6ce1 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -133,6 +133,8 @@ "偵測出網際網路連線失敗: %s" "無法使用網際網路" "錯誤碼: (#%d)" + "已連線,輕觸以檢查連線能力" + "未連線" QR 碼 @@ -148,7 +150,7 @@ 全球 略過中國大陸 - Geo 家族 + diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index f9e72cb..23d281e 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -2,8 +2,8 @@ chacha20-poly1305 - aes-128-cfb aes-128-gcm + auto none @@ -56,7 +56,7 @@ 0 1 - 2 + diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 43b0869..589de68 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -133,6 +133,8 @@ Fail to detect internet connection: %s Internet Unavailable Error code: #%d + Connected, tap to check connection + Not connected QRcode @@ -148,7 +150,7 @@ Global Bypass mainland - Geo Family + diff --git a/V2rayNG/app/src/main/res/values/styles.xml b/V2rayNG/app/src/main/res/values/styles.xml index 61d4eae..4eeb423 100644 --- a/V2rayNG/app/src/main/res/values/styles.xml +++ b/V2rayNG/app/src/main/res/values/styles.xml @@ -6,7 +6,7 @@ - @color/fab_orange_light + diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index aae1e17..3acf8c8 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -10,10 +10,10 @@ android:summary="@string/summary_pref_per_app_proxy" android:title="@string/title_pref_per_app_proxy" /> - + + + + - + + + Date: Mon, 10 Dec 2018 17:19:07 +0800 Subject: [PATCH 31/49] add --- V2rayNG/app/src/main/res/anim/fade_out.xml | 6 ++++++ .../main/res/drawable-xxhdpi/side_nav_bar.xml | 9 +++++++++ .../res/drawable/ic_attach_money_black_24dp.xml | 9 +++++++++ .../res/drawable/ic_attach_money_white_24dp.xml | 9 +++++++++ .../res/drawable/ic_description_black_24dp.xml | 9 +++++++++ .../res/drawable/ic_description_white_24dp.xml | 9 +++++++++ .../res/drawable/ic_feedback_white_24dp.xml | 9 +++++++++ .../app/src/main/res/drawable/nav_header_bg.png | Bin 0 -> 64447 bytes 8 files changed, 60 insertions(+) create mode 100644 V2rayNG/app/src/main/res/anim/fade_out.xml create mode 100644 V2rayNG/app/src/main/res/drawable-xxhdpi/side_nav_bar.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_attach_money_black_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_attach_money_white_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_description_black_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_description_white_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_feedback_white_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/nav_header_bg.png diff --git a/V2rayNG/app/src/main/res/anim/fade_out.xml b/V2rayNG/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..2b8bb1c --- /dev/null +++ b/V2rayNG/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,6 @@ + + diff --git a/V2rayNG/app/src/main/res/drawable-xxhdpi/side_nav_bar.xml b/V2rayNG/app/src/main/res/drawable-xxhdpi/side_nav_bar.xml new file mode 100644 index 0000000..6d81870 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable-xxhdpi/side_nav_bar.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/drawable/ic_attach_money_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_attach_money_black_24dp.xml new file mode 100644 index 0000000..b520fc9 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_attach_money_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_attach_money_white_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_attach_money_white_24dp.xml new file mode 100644 index 0000000..2b65f0c --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_attach_money_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_description_black_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_description_black_24dp.xml new file mode 100644 index 0000000..38c3335 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_description_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_description_white_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_description_white_24dp.xml new file mode 100644 index 0000000..7e0d28e --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_description_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_feedback_white_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_feedback_white_24dp.xml new file mode 100644 index 0000000..3e08fae --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_feedback_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/nav_header_bg.png b/V2rayNG/app/src/main/res/drawable/nav_header_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..d383f6a59d5957b3863ecb6ba92280898d70f8cd GIT binary patch literal 64447 zcmV)bK&iipP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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%0DIbJe9 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 Date: Mon, 10 Dec 2018 17:19:37 +0800 Subject: [PATCH 32/49] add2 --- .../app/src/main/assets/proxy_packagename.txt | 196 +++++++++++++++++ .../com/v2ray/ang/ui/BaseDrawerActivity.kt | 206 ++++++++++++++++++ V2rayNG/app/src/main/res/anim/fade_in.xml | 6 + .../app/src/main/res/layout/nav_header.xml | 24 ++ .../app/src/main/res/layout/nav_toolbar.xml | 15 ++ V2rayNG/app/src/main/res/layout/nav_view.xml | 11 + V2rayNG/app/src/main/res/menu/menu_drawer.xml | 41 ++++ .../app/src/main/res/values-v21/styles.xml | 8 + 8 files changed, 507 insertions(+) create mode 100644 V2rayNG/app/src/main/assets/proxy_packagename.txt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseDrawerActivity.kt create mode 100644 V2rayNG/app/src/main/res/anim/fade_in.xml create mode 100644 V2rayNG/app/src/main/res/layout/nav_header.xml create mode 100644 V2rayNG/app/src/main/res/layout/nav_toolbar.xml create mode 100644 V2rayNG/app/src/main/res/layout/nav_view.xml create mode 100644 V2rayNG/app/src/main/res/menu/menu_drawer.xml create mode 100644 V2rayNG/app/src/main/res/values-v21/styles.xml diff --git a/V2rayNG/app/src/main/assets/proxy_packagename.txt b/V2rayNG/app/src/main/assets/proxy_packagename.txt new file mode 100644 index 0000000..a603316 --- /dev/null +++ b/V2rayNG/app/src/main/assets/proxy_packagename.txt @@ -0,0 +1,196 @@ +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 +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 +be.mygod.vpnhotspot +com.alphainventor.filemanager +com.android.providers.downloads +com.apkpure.aegon +com.ballistiq.artstation +com.bitly.app +com.chrome.canary +com.chrome.dev +com.devhd.feedly +com.dropbox.android +com.estrongs.android.pop +com.estrongs.android.pop.pro +com.fastaccess.github +com.firstrowria.pushnotificationtester +com.fvd.eversync +com.gianlu.aria2app +com.github.yeriomin.yalpstore +com.google.android.apps.docs.editors.sheets +com.google.android.instantapps.supervisor +com.google.android.ogyoutube +com.google.android.partnersetup +com.google.android.syncadapters.calendar +com.google.android.syncadapters.contacts +com.google.android.tts +com.hochan.coldsoup +com.ifttt.ifttt +com.imgur.mobile +com.innologica.inoreader +com.instapaper.android +com.jarvanh.vpntether +com.mediapods.tumbpods +com.mgoogle.android.gms +com.microsoft.office.powerpoint +com.mixplorer +com.msd.consumerchinese +com.msd.professionalchinese +com.mss2011c.sharehelper +com.newin.nplayer.pro +com.oasisfeng.island +com.orekie.search +com.popularapp.videodownloaderforinstagram +com.pushbullet.android +com.rhmsoft.edit +com.slack +com.tencent.huatuo +com.termux +com.thunkable.android.hritvik00.freenom +com.topjohnwu.magisk +com.u91porn +com.u9porn +com.vimeo.android.videoapp +com.wuxiangai.refactor +com.yandex.browser +com.z28j.feel +de.robv.android.xposed.installer +dk.tacit.android.foldersync.full +es.rafalense.telegram.themes +es.rafalense.themes +flipboard.app +github.tornaco.xposedmoduletest +io.va.exposed +jp.pxv.android +me.tshine.easymark +net.teeha.android.url_shortener +onion.fire +org.fdroid.fdroid +org.mozilla.fennec_aurora +org.schabi.newpipe +org.telegram.messenger +org.torproject.android +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 \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseDrawerActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseDrawerActivity.kt new file mode 100644 index 0000000..879d3d9 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/BaseDrawerActivity.kt @@ -0,0 +1,206 @@ +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() + return + } + R.id.donate -> { + startActivity() + 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(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(R.id.drawer_layout) as DrawerLayout + if (mDrawerLayout != null) { + val navigationView = findViewById(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) + } + } +} diff --git a/V2rayNG/app/src/main/res/anim/fade_in.xml b/V2rayNG/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..29e0432 --- /dev/null +++ b/V2rayNG/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,6 @@ + + diff --git a/V2rayNG/app/src/main/res/layout/nav_header.xml b/V2rayNG/app/src/main/res/layout/nav_header.xml new file mode 100644 index 0000000..8eeb793 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/nav_header.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/nav_toolbar.xml b/V2rayNG/app/src/main/res/layout/nav_toolbar.xml new file mode 100644 index 0000000..d1a0d91 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/nav_toolbar.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/nav_view.xml b/V2rayNG/app/src/main/res/layout/nav_view.xml new file mode 100644 index 0000000..77b0d6c --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/nav_view.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_drawer.xml b/V2rayNG/app/src/main/res/menu/menu_drawer.xml new file mode 100644 index 0000000..c8a3c3d --- /dev/null +++ b/V2rayNG/app/src/main/res/menu/menu_drawer.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/V2rayNG/app/src/main/res/values-v21/styles.xml b/V2rayNG/app/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..fd7a058 --- /dev/null +++ b/V2rayNG/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,8 @@ + + + + From 4a83eb8d7dcd56baf98e75c2229fd9cf2fc335af Mon Sep 17 00:00:00 2001 From: 2dust Date: Mon, 10 Dec 2018 17:23:55 +0800 Subject: [PATCH 33/49] add3 --- .gitignore | 5 + V2rayNG/app/build.gradle | 39 +- V2rayNG/app/src/main/AndroidManifest.xml | 7 +- V2rayNG/app/src/main/assets/v2ray_config.json | 95 +---- .../main/kotlin/com/v2ray/ang/AppConfig.kt | 8 +- .../kotlin/com/v2ray/ang/dto/V2rayConfig.kt | 52 +-- .../com/v2ray/ang/service/V2RayVpnService.kt | 338 +++++++--------- .../kotlin/com/v2ray/ang/ui/LogcatActivity.kt | 10 +- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 135 +++++-- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 17 +- .../com/v2ray/ang/ui/PerAppProxyActivity.kt | 74 +++- .../com/v2ray/ang/ui/SettingsActivity.kt | 68 ++-- .../com/v2ray/ang/ui/SubSettingActivity.kt | 5 +- .../com/v2ray/ang/util/AngConfigManager.kt | 38 +- .../main/kotlin/com/v2ray/ang/util/Utils.kt | 72 ++-- .../com/v2ray/ang/util/V2rayConfigUtil.kt | 369 +++++------------- .../src/main/res/layout/activity_logcat.xml | 10 + .../app/src/main/res/layout/activity_main.xml | 134 ++++--- .../app/src/main/res/layout/activity_none.xml | 2 +- .../src/main/res/layout/activity_server2.xml | 16 +- .../main/res/layout/activity_sub_setting.xml | 6 +- .../src/main/res/menu/menu_bypass_list.xml | 6 + V2rayNG/app/src/main/res/menu/menu_main.xml | 48 ++- .../src/main/res/values-zh-rCN/strings.xml | 28 +- .../src/main/res/values-zh-rTW/strings.xml | 23 +- V2rayNG/app/src/main/res/values/arrays.xml | 4 +- V2rayNG/app/src/main/res/values/colors.xml | 8 +- V2rayNG/app/src/main/res/values/dimens.xml | 7 +- V2rayNG/app/src/main/res/values/strings.xml | 26 +- V2rayNG/app/src/main/res/values/styles.xml | 18 +- .../app/src/main/res/xml/pref_settings.xml | 42 +- V2rayNG/build.gradle | 11 +- V2rayNG/dpreference/build.gradle | 13 +- V2rayNG/gradle.properties | 10 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- V2rayNG/settings.gradle | 2 +- 36 files changed, 891 insertions(+), 859 deletions(-) diff --git a/.gitignore b/.gitignore index bd40895..6c90426 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,9 @@ 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 +*.aar +*.dat +*.jks +V2rayNG/gradle/wrapper/gradle-wrapper.properties V2rayNG/gradle/wrapper/gradle-wrapper.properties \ No newline at end of file diff --git a/V2rayNG/app/build.gradle b/V2rayNG/app/build.gradle index f7ff19b..9d6080c 100644 --- a/V2rayNG/app/build.gradle +++ b/V2rayNG/app/build.gradle @@ -4,15 +4,16 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.gms.google-services' android { - compileSdkVersion Integer.parseInt("$compileSdkVer") - buildToolsVersion "$buildToolsVer" + compileSdkVersion 27 + buildToolsVersion '27.0.3' defaultConfig { applicationId "com.v2ray.ang" minSdkVersion 17 targetSdkVersion Integer.parseInt("$targetSdkVer") - versionCode 105 - versionName "0.5.3" + multiDexEnabled true + versionCode 142 + versionName "0.6.4-beta2" } signingConfigs { @@ -73,13 +74,11 @@ android { } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' - implementation project(':libv2ray') implementation project(':dpreference') implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - // Android support library implementation "com.android.support:support-v4:$supportLibVersion" implementation "com.android.support:appcompat-v7:$supportLibVersion" @@ -87,39 +86,45 @@ dependencies { implementation "com.android.support:cardview-v7:$supportLibVersion" implementation "com.android.support:preference-v7:$supportLibVersion" implementation "com.android.support:recyclerview-v7:$supportLibVersion" - // 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.2' 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' - -// // LeakCanary -// debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' -// releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' -// testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' - + // // LeakCanary + // debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' + // releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' + // testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4' // Firebase implementation "com.google.firebase:firebase-core:$firebaseVersion" implementation "com.google.firebase:firebase-crash:$firebaseVersion" - 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' + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index a8e611a..6664399 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -18,7 +18,7 @@ android:theme="@style/AppTheme"> @@ -58,6 +58,7 @@ android:name=".ui.RoutingSettingsActivity" android:windowSoftInputMode="stateUnchanged" /> + @@ -90,10 +91,6 @@ - - - - ?, - var outbound: OutboundBean, - val outboundDetour: List, - val dns: DnsBean, - val routing: RoutingBean) { +data class V2rayConfig( + val log: LogBean, + //val inbounds: ArrayList, + var outbounds: ArrayList, + var dns: DnsBean, + val routing: RoutingBean) { data class LogBean(val access: String, val error: String, val loglevel: String) data class InboundBean( - val listen: String, - //val port: Int, - val protocol: String, + var port: Int, + var protocol: String, val settings: InSettingsBean, val sniffing: SniffingBean) { @@ -34,7 +31,8 @@ data class V2rayConfig(val port: Int, var mux: MuxBean) { data class OutSettingsBean(var vnext: List?, - var servers: List?) { + var servers: List?, + var response: Response) { data class VnextBean(var address: String, var port: Int, @@ -52,6 +50,7 @@ data class V2rayConfig(val port: Int, var port: Int, var level: Int) + data class Response(var type: String) } data class StreamSettingsBean(var network: String, @@ -92,34 +91,17 @@ data class V2rayConfig(val port: Int, data class TlssettingsBean(var allowInsecure: Boolean = true, var serverName: String = "") } - } - - data class MuxBean(var enabled: Boolean) - - data class InboundDetourBean( - var port: Int, - val listen: String, - val protocol: String, - val settings: InSettingsBean, - val sniffing: SniffingBean) { - data class InSettingsBean(val auth: String, - val udp: Boolean) - - data class SniffingBean(val enabled: Boolean, - val destOverride: List) + data class MuxBean(var enabled: Boolean) } - data class OutboundDetourBean(val protocol: String, - var settings: OutboundDetourSettingsBean, - val tag: String) { - data class OutboundDetourSettingsBean(var response: Response) { - data class Response(var type: String) - } + //data class DnsBean(var servers: List) + data class DnsBean(var servers: List) { + data class ServersBean(var address: String = "", + var port: Int = 0, + var domains: List = ArrayList()) } - data class DnsBean(var servers: List) - data class RoutingBean(val strategy: String, val settings: SettingsBean) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index 0ab5b55..efb963b 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -23,19 +23,18 @@ 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.AssetsUtil import com.v2ray.ang.util.MessageUtil -import com.v2ray.ang.util.Utils -import libv2ray.Libv2ray -import libv2ray.V2RayCallbacks -import libv2ray.V2RayVPNServiceSupportsSet -import org.jetbrains.anko.sp import rx.Observable import rx.Subscription -import java.io.FileDescriptor import java.io.FileInputStream -import java.io.PrintWriter +import java.io.FileOutputStream import java.lang.ref.SoftReference +import tun2socks.PacketFlow +import tun2socks.Tun2socks +import java.net.InetAddress +import java.nio.ByteBuffer +import kotlin.concurrent.thread +import tun2socks.VpnService as Tun2socksVpnService class V2RayVpnService : VpnService() { companion object { @@ -53,26 +52,25 @@ class V2RayVpnService : VpnService() { } } - private val v2rayPoint = Libv2ray.newV2RayPoint() - private val v2rayCallback = V2RayCallback() - // private var connectivitySubscription: Subscription? = null -// private var netWorkStateReceiver: NetWorkStateReceiver? = null + private val TAG = "V2RayVpnService:GoLog" + private lateinit var domainName: String private lateinit var configContent: String - private lateinit var mInterface: ParcelFileDescriptor - val fd: Int get() = mInterface.fd - private var currentTimeMillis: Long = 0 private var mBuilder: NotificationCompat.Builder? = null private var mSubscription: Subscription? = null private var lastVpnBandwidth: VpnBandwidth? = null private var mNotificationManager: NotificationManager? = null + var isRunning = false + + var proxyDomainIPMap: HashMap = HashMap() + var pfd: ParcelFileDescriptor? = null + var inputStream: FileInputStream? = null + var outputStream: FileOutputStream? = null + var buffer = ByteBuffer.allocate(1501) override fun onCreate() { super.onCreate() - val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() StrictMode.setThreadPolicy(policy) - - v2rayPoint.packageName = packageName } override fun onRevoke() { @@ -81,33 +79,61 @@ class V2RayVpnService : VpnService() { override fun onDestroy() { super.onDestroy() - cancelNotification() } - fun setup(parameters: String) { - // If the old interface has exactly the same parameters, use it! - // Configure a builder while parsing the parameters. - val builder = Builder() + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.d(TAG, "onStartCommand") + startV2ray() + return START_STICKY + } - parameters.split(" ") - .map { it.split(",") } - .forEach { - when (it[0][0]) { - 'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt()) - 'a' -> builder.addAddress(it[1], Integer.parseInt(it[2])) - 'r' -> builder.addRoute(it[1], Integer.parseInt(it[2])) - 's' -> builder.addSearchDomain(it[1]) - } - } + private fun startV2ray() { + if (isRunning) { + Log.d(TAG, "isRunning") + return + } - builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) + Log.d(TAG, "check prepare") + val prepare = VpnService.prepare(this) + if (prepare != null) { + return + } - val dnsServers = Utils.getRemoteDnsServers(defaultDPreference) - for (dns in dnsServers) { - builder.addDnsServer(dns) + Log.d(TAG, "registerReceiver mMsgReceive") + try { + registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)) + } catch (e: Exception) { } + domainName = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "") + configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") + Log.d(TAG, domainName) + + try { + val addr = InetAddress.getByName(domainName) + val ip = addr.getHostAddress() + if (domainName != ip) { + proxyDomainIPMap.put(domainName, ip) + Log.d(TAG, ip) + } + } catch (e: Exception) { + Log.d(TAG, e.message) + return + } + Log.d(TAG, "get InetAddress ok") + + val builder = Builder() + builder.setSession("vv") + .setMtu(1500) + .addAddress("10.233.233.233", 30) + .addDnsServer("223.5.5.5") + .addSearchDomain("local") + .addRoute("0.0.0.0", 0) +// builder.setBlocking(true) + + builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) + 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) @@ -119,22 +145,44 @@ class V2RayVpnService : VpnService() { else builder.addAllowedApplication(it) } catch (e: PackageManager.NameNotFoundException) { - //Logger.d(e) + Log.d(TAG, e.message) } } } - // Close the old interface since the parameters have been changed. - try { - mInterface.close() - } catch (ignored: Exception) { + pfd = builder.establish() + Log.d(TAG, "pfd:" + pfd.toString()) + + inputStream = FileInputStream(pfd?.fileDescriptor) + outputStream = FileOutputStream(pfd?.fileDescriptor) + + val flow = Flow(outputStream) + val service = Service(this) + + val files = filesDir.list() + if (!files.contains("geoip.dat") || !files.contains("geosite.dat")) { + Log.d(TAG, "not contains geo files") + val geoipBytes = resources.openRawResource(R.raw.geoip).readBytes() + val fos = openFileOutput("geoip.dat", Context.MODE_PRIVATE) + fos.write(geoipBytes) + fos.close() + + val geositeBytes = resources.openRawResource(R.raw.geosite).readBytes() + val fos2 = openFileOutput("geosite.dat", Context.MODE_PRIVATE) + fos2.write(geositeBytes) + fos2.close() } + val serverDomains = proxyDomainIPMap.keys.joinToString(separator = ",") + val serverIPs = proxyDomainIPMap.values.joinToString(separator = ",") - // Create a new interface using the builder and save the parameters. - mInterface = builder.establish() - //Logger.d("VPNService", "New interface: " + parameters) - //Logger.d(Libv2ray.checkVersionX()) + Log.d(TAG, "start Tun2socks") + //tun2socks.Tun2socks.startV2Ray(flow, service, configContent.toByteArray(), filesDir.absolutePath) + Tun2socks.startV2Ray(flow, service, configContent.toByteArray(), filesDir.absolutePath, serverDomains, serverIPs) + Log.d(TAG, "success Tun2socks") + isRunning = true + MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "") + showNotification() if (defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) { mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS) @@ -148,93 +196,31 @@ class V2RayVpnService : VpnService() { } } } - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - restartV2Ray() - return START_STICKY - //return super.onStartCommand(intent, flags, startId) - } - - private fun vpnCheckIsReady() { - val prepare = VpnService.prepare(this) - - if (prepare != null) { - return + thread(start = true) { + Log.d(TAG, "handlePackets") + handlePackets() + Log.d(TAG, "end handlePackets") } - - v2rayPoint.vpnSupportReady() - if (v2rayPoint.isRunning) { - MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "") - showNotification() - } else { - MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "") - cancelNotification() - } - } - - private fun startV2ray(isForced: Boolean = false) { - if (!v2rayPoint.isRunning || isForced) { - - try { - registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)) - } catch (e: Exception) { - } - - configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") - -// connectivitySubscription = ReactiveNetwork.observeNetworkConnectivity(this.applicationContext) -// .subscribeOn(Schedulers.io()) -// //.filter(Connectivity.hasState(NetworkInfo.State.CONNECTED)) -// //.throttleWithTimeout(3, TimeUnit.SECONDS) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe { connectivity -> -// val state = connectivity.state -// Logger.e(state.toString()) -// //if (state == NetworkInfo.State.CONNECTED) { -// if (v2rayPoint.isRunning) { -// v2rayPoint.networkInterrupted() -// } -// //} -// - v2rayPoint.callbacks = v2rayCallback -// v2rayPoint.vpnSupportSet = v2rayCallback - v2rayPoint.setVpnSupportSet(v2rayCallback) - - v2rayPoint.configureFile = "V2Ray_internal/ConfigureFileContent" - v2rayPoint.configureFileContent = configContent - - //next gen tun2ray -// v2rayPoint.upgradeToContext() -// v2rayPoint.optinNextGenerationTunInterface() - - v2rayPoint.runLoop() - } - -// showNotification() } - private fun stopV2Ray(isForced: Boolean = true) { + private fun stopV2Ray() { // val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "") // val emptyInfo = VpnNetworkInfo() // val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo) // saveVpnNetworkInfo(configName, info) - if (v2rayPoint.isRunning) { - v2rayPoint.stopLoop() - } - -// unregisterReceiver(netWorkStateReceiver) + if (isRunning) { + isRunning = false + Tun2socks.stopV2Ray() + pfd?.close() + pfd = null + inputStream = null + outputStream = null -// connectivitySubscription?.let { -// it.unsubscribe() -// connectivitySubscription = null -// } - MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "") - cancelNotification() + MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "") + cancelNotification() - if (isForced) { try { unregisterReceiver(mMsgReceive) } catch (e: Exception) { @@ -243,28 +229,17 @@ class V2RayVpnService : VpnService() { } } - private fun restartV2Ray() { - try { - //use custom geo dat -// val path = AssetsUtil.getAssetPath(this, "geoip.dat") -// Libv2ray.setAssetsOverride("geoip.dat", path) -// -// val path2 = AssetsUtil.getAssetPath(this, "geosite.dat") -// Libv2ray.setAssetsOverride("geosite.dat", path2) - Log.d("restartV2Ray", "restartV2Ray") - - //stopV2Ray(false) - startV2ray(true) - } catch (e: Exception) { + class Flow(stream: FileOutputStream?) : PacketFlow { + val flowOutputStream = stream + override fun writePacket(pkt: ByteArray?) { + flowOutputStream?.write(pkt) } } - private fun restartV2RaySoft() { - if (System.currentTimeMillis() > currentTimeMillis + 2000) { - if (v2rayPoint.isRunning) { - v2rayPoint.networkInterrupted() - } - currentTimeMillis = System.currentTimeMillis() + class Service(service: VpnService) : tun2socks.VpnService { + val vpnService = service + override fun protect(fd: Long) { + vpnService.protect(fd.toInt()) } } @@ -308,11 +283,6 @@ class V2RayVpnService : VpnService() { //mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使 startForeground(NOTIFICATION_ID, mBuilder?.build()) - -// if (netWorkStateReceiver == null) { -// netWorkStateReceiver = NetWorkStateReceiver() -// } -// registerReceiver(netWorkStateReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) } @RequiresApi(Build.VERSION_CODES.O) @@ -349,23 +319,6 @@ class V2RayVpnService : VpnService() { return mNotificationManager!! } -// private val vpnBandwidth: VpnBandwidth? -// get() = FileInputStream("/proc/net/dev").bufferedReader().use { -// val prefix = "tun0:" -// while (true) { -// val line = it.readLine().trim() -// if (line.startsWith(prefix)) { -// val numbers = line.substring(prefix.length).split(' ') -// .filter(String::isNotEmpty) -// .map(String::toLong) -// if (numbers.size > 10) -// return VpnBandwidth(numbers[0], numbers[8]) -// break -// } -// } -// return null -// } - private val vpnBandwidth: VpnBandwidth? get() { try { @@ -391,36 +344,6 @@ class V2RayVpnService : VpnService() { } } - - private inner class V2RayCallback : V2RayCallbacks, V2RayVPNServiceSupportsSet { - override fun shutdown() = 0L - - override fun getVPNFd() = this@V2RayVpnService.fd.toLong() - - override fun prepare(): Long { - vpnCheckIsReady() - return 1 - } - - 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) { - e.printStackTrace() - return -1 - } - } - } - private var mMsgReceive = ReceiveMessageHandler(this@V2RayVpnService) private class ReceiveMessageHandler(vpnService: V2RayVpnService) : BroadcastReceiver() { @@ -431,13 +354,12 @@ class V2RayVpnService : VpnService() { when (intent?.getIntExtra("key", 0)) { AppConfig.MSG_REGISTER_CLIENT -> { //Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString()) - - val isRunning = vpnService?.v2rayPoint!!.isRunning + val isRunning = vpnService?.isRunning!! && VpnService.prepare(vpnService) == null if (isRunning) { - MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_RUNNING, "") + MessageUtil.sendMsg2UI(vpnService!!, AppConfig.MSG_STATE_RUNNING, "") } else { - MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_NOT_RUNNING, "") + MessageUtil.sendMsg2UI(vpnService!!, AppConfig.MSG_STATE_NOT_RUNNING, "") } } AppConfig.MSG_UNREGISTER_CLIENT -> { @@ -450,13 +372,27 @@ class V2RayVpnService : VpnService() { vpnService?.stopV2Ray() } AppConfig.MSG_STATE_RESTART -> { - vpnService?.restartV2Ray() - } - AppConfig.MSG_STATE_RESTART_SOFT -> { - vpnService?.restartV2RaySoft() + vpnService?.startV2ray() } } } } + + fun handlePackets() { + while (isRunning) { + val n = inputStream?.read(buffer.array()) + n?.let { it } ?: return + if (n > 0) { + //Log.d(TAG, "handlePackets n:" + n.toString()) + buffer.limit(n) + tun2socks.Tun2socks.inputPacket(buffer.array()) + buffer.clear() + } +// if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { +// // In non-blocking mode +// Thread.sleep(50) +// } + } + } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt index 123bc74..c726163 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/LogcatActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.view.Menu import android.view.MenuItem +import android.view.View import com.v2ray.ang.R import com.v2ray.ang.util.Utils import kotlinx.android.synthetic.main.activity_logcat.* @@ -21,12 +22,16 @@ class LogcatActivity : BaseActivity() { setContentView(R.layout.activity_logcat) title = getString(R.string.title_logcat) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + logcat() } - override fun onPostResume() { - super.onPostResume() + private fun logcat() { + try { + pb_waiting.visibility = View.VISIBLE + doAsync { val lst = LinkedHashSet() lst.add("logcat") @@ -43,6 +48,7 @@ class LogcatActivity : BaseActivity() { uiThread { tv_logcat.text = allText tv_logcat.movementMethod = ScrollingMovementMethod() + pb_waiting.visibility = View.GONE } } } catch (e: IOException) { diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index 42768cc..5ddeafa 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -22,15 +22,19 @@ 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 com.v2ray.ang.extension.defaultDPreference import rx.Observable import rx.android.schedulers.AndroidSchedulers import java.util.concurrent.TimeUnit import com.v2ray.ang.helper.SimpleItemTouchHelperCallback -import kotlinx.android.synthetic.main.activity_server3.* -class MainActivity : BaseActivity() { +class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { companion object { private const val REQUEST_CODE_VPN_PREPARE = 0 private const val REQUEST_SCAN = 1 @@ -58,6 +62,8 @@ class MainActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + title = getString(R.string.title_server) + setSupportActionBar(toolbar) fab.setOnClickListener { if (isRunning) { @@ -72,17 +78,19 @@ class MainActivity : BaseActivity() { } } layout_test.setOnClickListener { - if (isRunning) { - tv_test_state.text = getString(R.string.connection_test_testing) - doAsync { - val result = Utils.testConnection(this@MainActivity) - uiThread { - tv_test_state.text = Utils.getEditable(result) - } - } - } else { -// tv_test_state.text = getString(R.string.connection_test_fail) - } +// if (isRunning) { +// val socksPort = 0//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) +// } +// } +// } else { +//// tv_test_state.text = getString(R.string.connection_test_fail) +// } } recycler_view.setHasFixedSize(true) @@ -92,9 +100,19 @@ class MainActivity : BaseActivity() { val callback = SimpleItemTouchHelperCallback(adapter) mItemTouchHelper = ItemTouchHelper(callback) mItemTouchHelper?.attachToRecyclerView(recycler_view) + + + val toggle = ActionBarDrawerToggle( + this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) + drawer_layout.addDrawerListener(toggle) + toggle.syncState() + nav_view.setNavigationItemSelectedListener(this) } fun startV2Ray() { + if (AngConfigManager.configs.index < 0) { + return + } showCircle() // toast(R.string.toast_services_start) Utils.startVService(this) @@ -178,6 +196,10 @@ class MainActivity : BaseActivity() { adapter.updateConfigList() true } + R.id.import_config_custom_clipboard -> { + importConfigCustomClipboard() + true + } R.id.import_config_custom_local -> { importConfigCustomLocal() true @@ -191,10 +213,10 @@ class MainActivity : BaseActivity() { true } - R.id.sub_setting -> { - startActivity() - true - } +// R.id.sub_setting -> { +// startActivity() +// true +// } R.id.sub_update -> { importConfigViaSub() @@ -209,14 +231,14 @@ class MainActivity : BaseActivity() { } true } - R.id.settings -> { - startActivity("isRunning" to isRunning) - true - } - R.id.logcat -> { - startActivity() - true - } +// R.id.settings -> { +// startActivity("isRunning" to isRunning) +// true +// } +// R.id.logcat -> { +// startActivity() +// true +// } else -> super.onOptionsItemSelected(item) } @@ -230,14 +252,14 @@ class MainActivity : BaseActivity() { // .addCategory(Intent.CATEGORY_DEFAULT) // .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) // } catch (e: Exception) { - RxPermissions(this) - .request(Manifest.permission.CAMERA) - .subscribe { - if (it) - startActivityForResult(requestCode) - else - toast(R.string.toast_permission_denied) - } + RxPermissions(this) + .request(Manifest.permission.CAMERA) + .subscribe { + if (it) + startActivityForResult(requestCode) + else + toast(R.string.toast_permission_denied) + } // } return true } @@ -266,6 +288,21 @@ class MainActivity : BaseActivity() { } } + fun importConfigCustomClipboard(): Boolean { + try { + val configText = Utils.getClipboard(this) + if (TextUtils.isEmpty(configText)) { + toast(R.string.toast_none_data_clipboard) + return false + } + importCustomizeConfig(configText) + return true + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + /** * import config from local config file */ @@ -467,4 +504,36 @@ class MainActivity : BaseActivity() { } 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() + } + R.id.settings -> { + startActivity("isRunning" to isRunning) + } + R.id.feedback -> { + Utils.openUri(this, "https://github.com/2dust/v2rayNG/issues") + } + R.id.donate -> { + startActivity() + } + R.id.logcat -> { + startActivity() + } + } + drawer_layout.closeDrawer(GravityCompat.START) + return true + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 84c14b8..1ca6c3a 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -9,8 +9,10 @@ import android.view.ViewGroup import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperViewHolder +import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.Utils import kotlinx.android.synthetic.main.activity_main.* @@ -78,7 +80,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter { + if (AngConfigManager.shareFullContent2Clipboard(position) == 0) { + mActivity.toast(R.string.toast_success) + } else { + mActivity.toast(R.string.toast_failure) + } + } else -> mActivity.toast("else") } @@ -200,9 +209,9 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter { val collator = Collator.getInstance() - override fun compare(o1: AppInfo, o2: AppInfo) - = collator.compare(o1.appName, o2.appName) + override fun compare(o1: AppInfo, o2: AppInfo) = collator.compare(o1.appName, o2.appName) } it.sortedWith(comparator) } @@ -176,8 +182,72 @@ class PerAppProxyActivity : BaseActivity() { it.notifyDataSetChanged() true } ?: false + R.id.select_proxy_app -> { + selectProxyApp() + true + } else -> super.onOptionsItemSelected(item) } + private fun selectProxyApp() { + toast(R.string.msg_downloading_content) + val url = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt" + doAsync { + val content = URL(url).readText() + uiThread { + Log.d("selectProxyApp", content) + selectProxyApp(content) + toast(R.string.toast_success) + } + } + } + + private fun selectProxyApp(content: String): 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 + } + + adapter?.blacklist!!.clear() + + if (switch_bypass_apps.isChecked) { + adapter?.let { + it.apps.forEach block@{ + val packageName = it.packageName + Log.d("selectProxyApp2", packageName) + if (proxyApps.indexOf(packageName) < 0) { + adapter?.blacklist!!.add(packageName) + println(packageName) + return@block + } + } + it.notifyDataSetChanged() + } + } else { + adapter?.let { + it.apps.forEach block@{ + val packageName = it.packageName + Log.d("selectProxyApp3", packageName) + if (proxyApps.indexOf(packageName) >= 0) { + adapter?.blacklist!!.add(packageName) + println(packageName) + return@block + } + } + it.notifyDataSetChanged() + } + } + } catch (e: Exception) { + e.printStackTrace() + return false + } + return true + } + } \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index 4f8a9bb..1d8c392 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -14,7 +14,6 @@ import com.v2ray.ang.R import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.onClick import com.v2ray.ang.util.Utils -import libv2ray.Libv2ray import org.jetbrains.anko.act import org.jetbrains.anko.defaultSharedPreferences import org.jetbrains.anko.startActivity @@ -25,17 +24,19 @@ class SettingsActivity : BaseActivity() { // const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland" // const val PREF_START_ON_BOOT = "pref_start_on_boot" const val PREF_PER_APP_PROXY = "pref_per_app_proxy" -// const val PREF_MUX_ENABLED = "pref_mux_enabled" +// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled" const val PREF_SPEED_ENABLED = "pref_speed_enabled" - const val PREF_REMOTE_DNS = "pref_remote_dns" - const val PREF_LANCONN_PORT = "pref_lanconn_port" +// const val PREF_REMOTE_DNS = "pref_remote_dns" + +// const val PREF_SOCKS_PORT = "pref_socks_port" +// const val PREF_LANCONN_PORT = "pref_lanconn_port" // const val PREF_SPEEDUP_DOMAIN = "pref_speedup_domain" const val PREF_ROUTING_MODE = "pref_routing_mode" const val PREF_ROUTING = "pref_routing" - const val PREF_DONATE = "pref_donate" +// const val PREF_DONATE = "pref_donate" // const val PREF_LICENSES = "pref_licenses" - const val PREF_FEEDBACK = "pref_feedback" +// const val PREF_FEEDBACK = "pref_feedback" // const val PREF_TG_GROUP = "pref_tg_group" const val PREF_VERSION = "pref_version" // const val PREF_AUTO_RESTART = "pref_auto_restart" @@ -45,19 +46,23 @@ class SettingsActivity : BaseActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) + title = getString(R.string.title_settings) + supportActionBar?.setDisplayHomeAsUpEnabled(true) } class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference } // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } - val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } - val lanconnPort by lazy { findPreference(PREF_LANCONN_PORT) as EditTextPreference } +// val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } + +// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference } +// val lanconnPort by lazy { findPreference(PREF_LANCONN_PORT) as EditTextPreference } val routing: Preference by lazy { findPreference(PREF_ROUTING) } - val donate: Preference by lazy { findPreference(PREF_DONATE) } +// val donate: Preference by lazy { findPreference(PREF_DONATE) } // val licenses: Preference by lazy { findPreference(PREF_LICENSES) } - val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } +// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } // val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) } val version: Preference by lazy { findPreference(PREF_VERSION) } @@ -69,9 +74,9 @@ class SettingsActivity : BaseActivity() { startActivity() } - donate.onClick { - startActivity() - } +// donate.onClick { +// startActivity() +// } // licenses.onClick { // val fragment = LicensesDialogFragment.Builder(act) @@ -80,10 +85,10 @@ class SettingsActivity : BaseActivity() { // .build() // fragment.show((act as AppCompatActivity).supportFragmentManager, null) // } - - feedback.onClick { - Utils.openUri(activity, "https://github.com/2dust/v2rayNG/issues") - } +// +// feedback.onClick { +// Utils.openUri(activity, "https://github.com/2dust/v2rayNG/issues") +// } // tgGroup.onClick { // // Utils.openUri(activity, "https://t.me/v2rayN") // val intent = Intent(Intent.ACTION_VIEW, Uri.parse("tg:resolve?domain=v2rayN")) @@ -101,25 +106,30 @@ class SettingsActivity : BaseActivity() { false } - remoteDns.setOnPreferenceChangeListener { preference, any -> - remoteDns.summary = any as String - true - } - - lanconnPort.setOnPreferenceChangeListener { preference, any -> - lanconnPort.summary = any as String - true - } +// remoteDns.setOnPreferenceChangeListener { preference, any -> +// remoteDns.summary = any as String +// true +// } +// socksPort.setOnPreferenceChangeListener { preference, any -> +// socksPort.summary = any as String +// true +// } +// lanconnPort.setOnPreferenceChangeListener { preference, any -> +// lanconnPort.summary = any as String +// true +// } - version.summary = "${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})" + version.summary = "${BuildConfig.VERSION_NAME} (Core V4.3)" } override fun onStart() { super.onStart() perAppProxy.isChecked = defaultSharedPreferences.getBoolean(PREF_PER_APP_PROXY, false) - remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "") - lanconnPort.summary = defaultSharedPreferences.getString(PREF_LANCONN_PORT, "") +// remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "") + +// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808") +// lanconnPort.summary = defaultSharedPreferences.getString(PREF_LANCONN_PORT, "") defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this) } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt index 82179b3..709ef38 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SubSettingActivity.kt @@ -16,15 +16,16 @@ class SubSettingActivity : BaseActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_sub_setting) + title = getString(R.string.title_sub_setting) + recycler_view.setHasFixedSize(true) recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.adapter = adapter - title = getString(R.string.title_sub_setting) supportActionBar?.setDisplayHomeAsUpEnabled(true) } - public override fun onResume() { + override fun onResume() { super.onResume() adapter.updateConfigList() } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 57dfc72..62e937c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -15,6 +15,8 @@ import com.v2ray.ang.AppConfig.VMESS_PROTOCOL import com.v2ray.ang.R import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.VmessQRCode +import com.v2ray.ang.extension.defaultDPreference +import org.jetbrains.anko.toast import java.net.URLDecoder import java.util.* @@ -41,6 +43,9 @@ object AngConfigManager { angConfig = Gson().fromJson(context, AngConfig::class.java) } else { angConfig = AngConfig(0, vmess = arrayListOf(AngConfig.VmessBean()), subItem = arrayListOf(AngConfig.SubItemBean())) + angConfig.index = -1 + angConfig.vmess.clear() + angConfig.subItem.clear() } for (i in angConfig.vmess.indices) { @@ -167,9 +172,20 @@ object AngConfigManager { /** * gen and store v2ray config file */ - fun genStoreV2rayConfig(): Boolean { + fun genStoreV2rayConfig(index: Int): Boolean { try { - val result = V2rayConfigUtil.getV2rayConfig(app, angConfig) + if (angConfig.index < 0 + || angConfig.vmess.count() <= 0 + || angConfig.index > angConfig.vmess.count() - 1 + ) { + return false + } + var index2 = angConfig.index + if (index >= 0) { + index2 = index + } + + val result = V2rayConfigUtil.getV2rayConfig(app, angConfig.vmess[index2]) if (result.status) { app.defaultDPreference.setPrefString(PREF_CURR_CONFIG, result.content) app.defaultDPreference.setPrefString(PREF_CURR_CONFIG_GUID, currConfigGuid()) @@ -447,6 +463,24 @@ object AngConfigManager { } } + /** + * shareFullContent2Clipboard + */ + fun shareFullContent2Clipboard(index: Int): Int { + try { + if (AngConfigManager.genStoreV2rayConfig(index)) { + val configContent = app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") + Utils.setClipboard(app.applicationContext, configContent) + } else { + return -1 + } + } catch (e: Exception) { + e.printStackTrace() + return -1 + } + return 0 + } + /** * import customize config */ diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt index 5f81e56..ebf888b 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/Utils.kt @@ -14,12 +14,14 @@ import kotlin.collections.HashMap import android.app.ActivityManager import android.content.ClipData import android.content.Intent +import android.content.res.AssetManager import android.net.Uri import android.os.SystemClock import android.text.TextUtils import android.util.Log import android.util.Patterns import android.webkit.URLUtil +import com.v2ray.ang.AngApplication import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.extension.responseLength @@ -27,7 +29,10 @@ import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.ui.SettingsActivity import me.dozen.dpreference.DPreference import org.jetbrains.anko.toast +import java.io.BufferedReader +import java.io.File import java.io.IOException +import java.io.InputStreamReader import java.net.* @@ -120,32 +125,20 @@ object Utils { /** * get remote dns servers from preference */ - fun getRemoteDnsServers(defaultDPreference: DPreference): List { - val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, "") - val ret = ArrayList() - if (!TextUtils.isEmpty(remoteDns)) { - remoteDns - .split(",") - .forEach { - if (Utils.isIpAddress(it)) { - ret.add(it) - } - } - } - - if (ret.size <= 0) { - if (!ret.contains("8.8.8.8")) { - ret.add("8.8.8.8") - } - if (!ret.contains("8.8.4.4")) { - ret.add("8.8.4.4") - } - } -// if (!ret.contains("localhost")) { -// ret.add("localhost") +// fun getRemoteDnsServers(defaultDPreference: DPreference): List { +// val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, "") +// val ret = ArrayList() +// if (!TextUtils.isEmpty(remoteDns)) { +// remoteDns +// .split(",") +// .forEach { +// if (Utils.isIpAddress(it)) { +// ret.add(it) +// } +// } // } - return ret - } +// return ret +// } /** * create qrcode using zxing @@ -260,7 +253,7 @@ object Utils { */ fun startVService(context: Context): Boolean { context.toast(R.string.toast_services_start) - if (AngConfigManager.genStoreV2rayConfig()) { + if (AngConfigManager.genStoreV2rayConfig(-1)) { V2RayVpnService.startV2Ray(context) return true } else { @@ -330,7 +323,7 @@ object Utils { /** * Based on: https://android.googlesource.com/platform/frameworks/base/+/b19a838/services/core/java/com/android/server/connectivity/NetworkMonitor.java#1071 */ - fun testConnection(context: Context): String { + fun testConnection(context: Context, port: Int): String { var result: String var conn: HttpURLConnection? = null @@ -341,7 +334,7 @@ object Utils { // Log.d("testConnection", "222222222222") conn = url.openConnection(Proxy(Proxy.Type.SOCKS, - InetSocketAddress("localhost", 10808))) + InetSocketAddress("localhost", port))) as HttpURLConnection // Log.d("testConnection", "333333333333") @@ -372,6 +365,29 @@ object Utils { return result } + + /** + * package path + */ + fun packagePath(context: Context): String { + var path = context.filesDir.toString() + path = path.replace("files", "") + //path += "tun2socks" + + return path + } + + + /** + * readTextFromAssets + */ + fun readTextFromAssets(app: AngApplication, fileName: String): String { + val content = app.assets.open(fileName).bufferedReader().use { + it.readText() + } + return content + } + } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 1c16bd9..4c8287c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -1,63 +1,18 @@ package com.v2ray.ang.util -import android.os.Build import android.text.TextUtils import android.util.Log import com.google.gson.Gson import com.v2ray.ang.AngApplication import com.v2ray.ang.AppConfig -import com.v2ray.ang.dto.AngConfig +import com.v2ray.ang.dto.AngConfig.VmessBean import com.v2ray.ang.dto.V2rayConfig -import com.v2ray.ang.extension.putOpt import com.v2ray.ang.ui.SettingsActivity import org.json.JSONArray import org.json.JSONException import org.json.JSONObject -import java.util.logging.Logger object V2rayConfigUtil { - private val lib2rayObj: JSONObject by lazy { - JSONObject("""{ - "enabled": true, - "listener": { - "onUp": "#none", - "onDown": "#none" - }, - "env": [ - "V2RaySocksPort=10808" - ], - "render": [], - "escort": [], - "vpnservice": { - "Target": "${"$"}{datadir}tun2socks", - "Args": [ - "--netif-ipaddr", - "26.26.26.2", - "--netif-netmask", - "255.255.255.0", - "--socks-server-addr", - "127.0.0.1:${"$"}V2RaySocksPort", - "--tunfd", - "3", - "--tunmtu", - "1500", - "--sock-path", - "/dev/null", - "--loglevel", - "4", - "--enable-udprelay" - ], - "VPNSetupArg": "m,1500 a,26.26.26.1,24 r,0.0.0.0,0" - }, - "preparedDomainName": { - "domainName": [ - ], - "tcpVersion": "tcp4", - "udpVersion": "udp4" - } - }""") - } - private val requestObj: JSONObject by lazy { JSONObject("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") } @@ -65,56 +20,28 @@ object V2rayConfigUtil { JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""") } - private val replacementPairs by lazy { - mapOf("port" to 10808, - "inbound" to JSONObject("""{ - "protocol": "socks", - "listen": "127.0.0.1", - "settings": { - "auth": "noauth", - "udp": true - }, - "sniffing": { - "enabled": true, - "destOverride": [ - "http", - "tls" - ] - } - }"""), - //"inboundDetour" to JSONArray(), - "#lib2ray" to lib2rayObj, - "log" to JSONObject("""{ - "loglevel": "warning" - }""") - ) - } - private val ruleDirectDnsObj: JSONObject by lazy { - JSONObject("""{"type":"field","port":53,"network":"udp","outboundTag":"directout"}""") - } - data class Result(var status: Boolean, var content: String) /** * 生成v2ray的客户端配置文件 */ - fun getV2rayConfig(app: AngApplication, config: AngConfig): Result { + fun getV2rayConfig(app: AngApplication, vmess: VmessBean): Result { var result = Result(false, "") try { //检查设置 - if (config.index < 0 - || config.vmess.count() <= 0 - || config.index > config.vmess.count() - 1 - ) { - return result - } +// if (config.index < 0 +// || config.vmess.count() <= 0 +// || config.index > config.vmess.count() - 1 +// ) { +// return result +// } - if (config.vmess[config.index].configType == AppConfig.EConfigType.Vmess) { - result = getV2rayConfigType1(app, config) - } else if (config.vmess[config.index].configType == AppConfig.EConfigType.Custom) { - result = getV2rayConfigType2(app, config) - } else if (config.vmess[config.index].configType == AppConfig.EConfigType.Shadowsocks) { - result = getV2rayConfigType1(app, config) + if (vmess.configType == AppConfig.EConfigType.Vmess) { + result = getV2rayConfigType1(app, vmess) + } else if (vmess.configType == AppConfig.EConfigType.Custom) { + result = getV2rayConfigType2(app, vmess) + } else if (vmess.configType == AppConfig.EConfigType.Shadowsocks) { + result = getV2rayConfigType1(app, vmess) } Log.d("V2rayConfigUtil", result.content) return result @@ -127,19 +54,11 @@ object V2rayConfigUtil { /** * 生成v2ray的客户端配置文件 */ - private fun getV2rayConfigType1(app: AngApplication, config: AngConfig): Result { + private fun getV2rayConfigType1(app: AngApplication, vmess: VmessBean): Result { val result = Result(false, "") try { - //检查设置 - if (config.index < 0 - || config.vmess.count() <= 0 - || config.index > config.vmess.count() - 1 - ) { - return result - } - //取得默认配置 - val assets = AssetsUtil.readTextFromAssets(app.assets, "v2ray_config.json") + val assets = Utils.readTextFromAssets(app, "v2ray_config.json") if (TextUtils.isEmpty(assets)) { return result } @@ -150,21 +69,15 @@ object V2rayConfigUtil { // return result // } - inbound(config, v2rayConfig, app) + //inbounds(vmess, v2rayConfig, app) - //vmess协议服务器配置 - outbound(config, v2rayConfig, app) + outbounds(vmess, v2rayConfig, app) - //routing - routing(config, v2rayConfig, app) + routing(vmess, v2rayConfig, app) - //dns - customDns(config, v2rayConfig, app) + customDns(vmess, v2rayConfig, app) - //增加lib2ray - val finalConfig = addLib2ray(v2rayConfig, app) - - //Log.d("config", finalConfig) + val finalConfig = Gson().toJson(v2rayConfig) result.status = true result.content = finalConfig @@ -179,25 +92,14 @@ object V2rayConfigUtil { /** * 生成v2ray的客户端配置文件 */ - private fun getV2rayConfigType2(app: AngApplication, config: AngConfig): Result { + private fun getV2rayConfigType2(app: AngApplication, vmess: VmessBean): Result { val result = Result(false, "") try { - //检查设置 - if (config.index < 0 - || config.vmess.count() <= 0 - || config.index > config.vmess.count() - 1 - ) { - return result - } - val vmess = config.vmess[config.index] val guid = vmess.guid val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "") - //增加lib2ray - val finalConfig = addLib2ray2(jsonConfig) - result.status = true - result.content = finalConfig + result.content = jsonConfig return result } catch (e: Exception) { @@ -209,37 +111,39 @@ object V2rayConfigUtil { /** * */ - private fun inbound(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { - try { - val lanconnPort = app.defaultDPreference.getPrefString(SettingsActivity.PREF_LANCONN_PORT, "") - val port = Utils.parseInt(lanconnPort) - - if (port == 0) { - v2rayConfig.inboundDetour = null - } else { - if (v2rayConfig.inboundDetour!!.isNotEmpty()) { - v2rayConfig.inboundDetour!!.get(0).port = port - } - } - } catch (e: Exception) { - e.printStackTrace() - return false - } - return true - } +// private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { +// try { +// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808")) +// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_LANCONN_PORT, "")) +// +// if (socksPort > 0) { +// v2rayConfig.inbounds[0].port = socksPort +// } +// if (lanconnPort > 0) { +// val httpCopy = v2rayConfig.inbounds[0].copy() +// httpCopy.port = lanconnPort +// httpCopy.protocol = "http" +// v2rayConfig.inbounds.add(httpCopy) +// } +// } catch (e: Exception) { +// e.printStackTrace() +// return false +// } +// return true +// } /** * vmess协议服务器配置 */ - private fun outbound(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { + private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { - val vmess = config.vmess[config.index] + val outbound = v2rayConfig.outbounds[0] when (vmess.configType) { AppConfig.EConfigType.Vmess -> { - v2rayConfig.outbound.settings.servers = null + outbound.settings.servers = null - val vnext = v2rayConfig.outbound.settings.vnext?.get(0) + val vnext = v2rayConfig.outbounds[0].settings.vnext?.get(0) vnext?.address = vmess.address vnext?.port = vmess.port val user = vnext?.users?.get(0) @@ -249,18 +153,17 @@ object V2rayConfigUtil { //Mux val muxEnabled = false//app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false) - v2rayConfig.outbound.mux.enabled = muxEnabled + outbound.mux.enabled = muxEnabled //远程服务器底层传输配置 - v2rayConfig.outbound.streamSettings = boundStreamSettings(config) - - v2rayConfig.outbound.protocol = "vmess" + outbound.streamSettings = boundStreamSettings(vmess) + outbound.protocol = "vmess" } AppConfig.EConfigType.Shadowsocks -> { - v2rayConfig.outbound.settings.vnext = null + outbound.settings.vnext = null - val server = v2rayConfig.outbound.settings.servers?.get(0) + val server = outbound.settings.servers?.get(0) server?.address = vmess.address server?.method = vmess.security server?.ota = false @@ -269,34 +172,17 @@ object V2rayConfigUtil { server?.level = 1 //Mux - v2rayConfig.outbound.mux.enabled = false - - v2rayConfig.outbound.protocol = "shadowsocks" + outbound.mux.enabled = false + outbound.protocol = "shadowsocks" } else -> { } } - //如果非ip - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (!Utils.isIpAddress(vmess.address)) { - val addr = String.format("%s:%s", vmess.address, vmess.port) - val domainName = lib2rayObj.optJSONObject("preparedDomainName") - .optJSONArray("domainName") - if (domainName.length() > 0) { - for (index in 0 until domainName.length()) { - domainName.remove(index) - } - } - domainName.put(addr) - } - } else { - if (!Utils.isIpAddress(vmess.address)) { - lib2rayObj.optJSONObject("preparedDomainName") - .optJSONArray("domainName") - .put(String.format("%s:%s", vmess.address, vmess.port)) - } + if (!Utils.isIpAddress(vmess.address)) { +// app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, String.format("%s:%s", vmess.address, vmess.port)) + app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, vmess.address) } } catch (e: Exception) { e.printStackTrace() @@ -308,12 +194,12 @@ object V2rayConfigUtil { /** * 远程服务器底层传输配置 */ - private fun boundStreamSettings(config: AngConfig): V2rayConfig.OutboundBean.StreamSettingsBean { + private fun boundStreamSettings(vmess: VmessBean): V2rayConfig.OutboundBean.StreamSettingsBean { val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null, null, null) try { //远程服务器底层传输配置 - streamSettings.network = config.vmess[config.index].network - streamSettings.security = config.vmess[config.index].streamSecurity + streamSettings.network = vmess.network + streamSettings.security = vmess.streamSecurity //streamSettings when (streamSettings.network) { @@ -327,14 +213,14 @@ object V2rayConfigUtil { kcpsettings.readBufferSize = 1 kcpsettings.writeBufferSize = 1 kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean.HeaderBean() - kcpsettings.header.type = config.vmess[config.index].headerType + kcpsettings.header.type = vmess.headerType streamSettings.kcpsettings = kcpsettings } "ws" -> { val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean() wssettings.connectionReuse = true - val host = config.vmess[config.index].requestHost.trim() - val path = config.vmess[config.index].path.trim() + val host = vmess.requestHost.trim() + val path = vmess.path.trim() if (!TextUtils.isEmpty(host)) { wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean.HeadersBean() @@ -351,8 +237,8 @@ object V2rayConfigUtil { } "h2" -> { val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpsettingsBean() - val host = config.vmess[config.index].requestHost.trim() - val path = config.vmess[config.index].path.trim() + val host = vmess.requestHost.trim() + val path = vmess.path.trim() if (!TextUtils.isEmpty(host)) { httpsettings.host = host.split(",").map { it.trim() } @@ -366,16 +252,16 @@ object V2rayConfigUtil { } else -> { //tcp带http伪装 - if (config.vmess[config.index].headerType == "http") { + if (vmess.headerType == "http") { val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean() tcpSettings.connectionReuse = true tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean.HeaderBean() - tcpSettings.header.type = config.vmess[config.index].headerType + tcpSettings.header.type = vmess.headerType if (requestObj.has("headers") || requestObj.optJSONObject("headers").has("Pragma")) { val arrHost = JSONArray() - config.vmess[config.index].requestHost + vmess.requestHost .split(",") .forEach { arrHost.put(it) @@ -399,7 +285,7 @@ object V2rayConfigUtil { /** * routing */ - private fun routing(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { + private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig) routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig) @@ -409,27 +295,16 @@ object V2rayConfigUtil { when (routingMode) { "0" -> { } - "1", "2" -> { + "1" -> { + routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) + } + "2" -> { routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) -// routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) -// routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) + } + "3" -> { routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) -// routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) + routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) } -// "2" -> { -// routingGeo("", "custom:reject", AppConfig.TAG_BLOCKED, v2rayConfig) -// routingGeo("", "surge:reject", AppConfig.TAG_BLOCKED, v2rayConfig) -// -// routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig) -// routingGeo("", "custom:direct", AppConfig.TAG_DIRECT, v2rayConfig) -// routingGeo("", "surge:direct", AppConfig.TAG_DIRECT, v2rayConfig) -// routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) -// routingGeo("domain", "gfwlist:direct", AppConfig.TAG_DIRECT, v2rayConfig) -// -// routingGeo("", "custom:proxy", AppConfig.TAG_AGENT, v2rayConfig) -// routingGeo("", "surge:proxy", AppConfig.TAG_AGENT, v2rayConfig) -// routingGeo("domain", "gfwlist:proxy", AppConfig.TAG_AGENT, v2rayConfig) -// } } } catch (e: Exception) { e.printStackTrace() @@ -489,7 +364,8 @@ object V2rayConfigUtil { } else if (Utils.isValidUrl(it) || it.startsWith("geosite:") || it.startsWith("regexp:") - || it.startsWith("domain:")) { + || it.startsWith("domain:") + || it.startsWith("full:")) { rulesDomain.domain?.add(it) } } @@ -500,8 +376,6 @@ object V2rayConfigUtil { v2rayConfig.routing.settings.rules.add(rulesIP) } } - - } catch (e: Exception) { e.printStackTrace() } @@ -510,9 +384,27 @@ object V2rayConfigUtil { /** * Custom Dns */ - private fun customDns(config: AngConfig, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { + private fun customDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { try { - v2rayConfig.dns.servers = Utils.getRemoteDnsServers(app.defaultDPreference) + val servers = ArrayList() + servers.add("1.1.1.1") + val server = V2rayConfig.DnsBean.ServersBean("223.5.5.5", 53, arrayListOf("geosite:cn")) + servers.add(server) + v2rayConfig.dns = V2rayConfig.DnsBean(servers) + +// val dns = Utils.getRemoteDnsServers(app.defaultDPreference) +// if (dns.count() > 0) { +// v2rayConfig.dns = V2rayConfig.DnsBean(Utils.getRemoteDnsServers(app.defaultDPreference)) + +// val servers = ArrayList() +// dns.forEach { +// val one = V2rayConfig.DnsBean.ServersBean() +// one.address = it +// one.port = 53 +// servers.add(one) +// } +// v2rayConfig.dns = V2rayConfig.DnsBean(servers) +// } } catch (e: Exception) { e.printStackTrace() return false @@ -520,73 +412,16 @@ object V2rayConfigUtil { return true } - - /** - * 增加lib2ray - */ - private fun addLib2ray(v2rayConfig: V2rayConfig, app: AngApplication): String { - try { - val conf = Gson().toJson(v2rayConfig) - val jObj = JSONObject(conf) - jObj.put("#lib2ray", lib2rayObj) - -// val speedupDomain = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEEDUP_DOMAIN, false) -// if (speedupDomain) { -// jObj.optJSONObject("routing") -// .optJSONObject("settings") -// .optJSONArray("rules") -// .put(0, ruleDirectDnsObj) -// } - - return jObj.toString() - } catch (e: Exception) { - e.printStackTrace() - return "" - } - } - - /** - * 增加lib2ray - */ - private fun addLib2ray2(jsonConfig: String): String { - try { - val jObj = JSONObject(jsonConfig) - //find outbound address and port - try { - if (jObj.has("outbound") - || jObj.optJSONObject("outbound").has("settings") - || jObj.optJSONObject("outbound").optJSONObject("settings").has("vnext")) { - val vnext = jObj.optJSONObject("outbound").optJSONObject("settings").optJSONArray("vnext") - for (i in 0..(vnext.length() - 1)) { - val item = vnext.getJSONObject(i) - val address = item.getString("address") - val port = item.getString("port") - if (!Utils.isIpAddress(address)) { - lib2rayObj.optJSONObject("preparedDomainName") - .optJSONArray("domainName") - .put(String.format("%s:%s", address, port)) - } - } - } - } catch (e: Exception) { - e.printStackTrace() - } - - jObj.putOpt(replacementPairs) - return jObj.toString() - } catch (e: Exception) { - e.printStackTrace() - return "" - } - } - /** * is valid config */ fun isValidConfig(conf: String): Boolean { try { val jObj = JSONObject(conf) - return jObj.has("outbound") and jObj.has("inbound") + var hasBound = false + //hasBound = (jObj.has("outbounds") and jObj.has("inbounds")) or (jObj.has("outbound") and jObj.has("inbound")) + hasBound = (jObj.has("outbounds")) or (jObj.has("outbound")) + return hasBound } catch (e: JSONException) { return false } diff --git a/V2rayNG/app/src/main/res/layout/activity_logcat.xml b/V2rayNG/app/src/main/res/layout/activity_logcat.xml index 2e9042a..06916be 100644 --- a/V2rayNG/app/src/main/res/layout/activity_logcat.xml +++ b/V2rayNG/app/src/main/res/layout/activity_logcat.xml @@ -6,6 +6,14 @@ android:padding="10dp" tools:context=".ui.MainActivity"> + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_main.xml b/V2rayNG/app/src/main/res/layout/activity_main.xml index 0a090be..ace1e69 100644 --- a/V2rayNG/app/src/main/res/layout/activity_main.xml +++ b/V2rayNG/app/src/main/res/layout/activity_main.xml @@ -1,71 +1,109 @@ - + android:fitsSystemWindows="true"> - + android:layout_height="match_parent" + android:orientation="vertical"> - + android:layout_height="wrap_content" + android:theme="@style/AppTheme.AppBarOverlay"> - + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + app:popupTheme="@style/AppTheme.PopupOverlay" /> - + + + + + android:layout_height="match_parent"> - + + - - + + - - + + + + + + + - - - + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_none.xml b/V2rayNG/app/src/main/res/layout/activity_none.xml index dcc9bf8..1aa97ae 100644 --- a/V2rayNG/app/src/main/res/layout/activity_none.xml +++ b/V2rayNG/app/src/main/res/layout/activity_none.xml @@ -2,5 +2,5 @@ + android:background="@color/colorPrimary"> diff --git a/V2rayNG/app/src/main/res/layout/activity_server2.xml b/V2rayNG/app/src/main/res/layout/activity_server2.xml index 5361d3f..25487a0 100644 --- a/V2rayNG/app/src/main/res/layout/activity_server2.xml +++ b/V2rayNG/app/src/main/res/layout/activity_server2.xml @@ -11,6 +11,20 @@ android:orientation="vertical" android:padding="@dimen/layout_margin_top_height"> + + + + + + + + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml b/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml index 7b0ba86..033da71 100644 --- a/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml +++ b/V2rayNG/app/src/main/res/layout/activity_sub_setting.xml @@ -1,4 +1,5 @@ + - - - - + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_bypass_list.xml b/V2rayNG/app/src/main/res/menu/menu_bypass_list.xml index ea2d2f4..dae2bcf 100644 --- a/V2rayNG/app/src/main/res/menu/menu_bypass_list.xml +++ b/V2rayNG/app/src/main/res/menu/menu_bypass_list.xml @@ -6,4 +6,10 @@ android:icon="@drawable/ic_select_all_white_24dp" android:title="@string/menu_item_select_all" app:showAsAction="always" /> + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_main.xml b/V2rayNG/app/src/main/res/menu/menu_main.xml index ce66fb4..ccb8706 100644 --- a/V2rayNG/app/src/main/res/menu/menu_main.xml +++ b/V2rayNG/app/src/main/res/menu/menu_main.xml @@ -24,17 +24,28 @@ app:showAsAction="never" /> - - + android:icon="@drawable/ic_add_white_24dp" + android:title="@string/menu_item_import_config_custom" + app:showAsAction="always"> + + + + + + + - - - \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index 0d67f8d..752e9c5 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -4,10 +4,12 @@ 开关 开关 初次使用此功能请先用APP激活VPN + Open navigation drawer + Close navigation drawer 停止 - 无法取得权限 + 无法取得权D:\vssHotel\SourceCode\Hotel.root\Hotel\Clubank.Hotel\FrontCounter\CheckoutListForm.cs限 点击了解更多 启动服务中 关闭中 @@ -15,7 +17,7 @@ 启动服务失败 - 服务器 + 配置文件 添加配置 保存配置 删除配置 @@ -23,8 +25,10 @@ 从剪贴板导入 手动输入[Vmess] 手动输入[Shadowsocks] + 自定义配置 + 从剪贴板导入自定义配置 从本地导入自定义配置 - 从URL导入自定义配置 + 剪贴板URL导入自定义配置 扫描URL导入自定义配置 确认删除? 别名(remarks) @@ -56,12 +60,15 @@ 内容 剪贴板中没有数据 无效的网址 + 确保inbound有socks代理端口10808 正在加载 全选 输入关键字 绕行模式 + 自动选中需代理应用 + 正在下载内容 设置 @@ -84,11 +91,14 @@ 支持开发者 陆续增加一些试验性的进阶功能 - 远程DNS + 远程DNS(可以不填) 远程DNS - 局域网的连接端口(0=不允许) - 局域网的连接端口 + SOCKS5代理端口 + SOCKS5代理端口 + + HTTP代理端口(0=不允许) + HTTP代理端口 加速域名解析 直连解析,可能存在问题,http/tls有效 @@ -138,8 +148,9 @@ 二维码 导出至剪贴板 + 导出完整配置至剪贴板 - + share_method 代理的网址或IP 直连的网址或IP @@ -148,8 +159,9 @@ 全局 + 绕过局域网地址 绕过大陆地址 - + 绕过局域网及大陆地址 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index 1bc6ce1..99f8dc2 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -4,6 +4,8 @@ 切換 切換 首次使用此功能,請使用此應用程式來啟用 VPN + Open navigation drawer + Close navigation drawer 停止 @@ -15,7 +17,7 @@ 啟動服務失敗 - 伺服器 + 組態 新增組態 儲存組態 刪除組態 @@ -23,6 +25,8 @@ 自剪貼簿匯入組態 手動鍵入[Vmess] 手動鍵入[Shadowsocks] + 自訂組態 + 自剪貼簿匯入自訂組態 自本機匯入自訂組態 自網址匯入自訂組態 掃描網址匯入自訂組態 @@ -56,12 +60,15 @@ 內容 剪貼簿內無資料 網址無效 + ​​確保inbound有socks代理端口10808 載入 全選 輸入關鍵字 略過模式 + 自動選中需代理應用 + 正在下載內容 @@ -85,11 +92,14 @@ 向開發人員捐款 新增一些實驗性進階功能 - 遠端 DNS + 遠端 DNS(可以不填) 遠端 DNS - 局域網的連接端口(0=不允許) - 局域網的連接端口 + SOCKS5代理端口 + SOCKS5代理端口 + + HTTP代理端口(0=不允許) + HTTP代理端口 加快網域名稱解析速度 只對 HTTP/TLS 有效 @@ -139,6 +149,7 @@ QR 碼 匯出至剪貼簿 + 匯出完整配置至剪貼簿 @@ -149,8 +160,8 @@ 全球 + 略過局域網 略過中國大陸 - + 略過局域網及中國大陸 - diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 23d281e..7fd0a68 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -56,7 +56,7 @@ 0 1 - + 2 + 3 - diff --git a/V2rayNG/app/src/main/res/values/colors.xml b/V2rayNG/app/src/main/res/values/colors.xml index 4fc2f81..c970961 100644 --- a/V2rayNG/app/src/main/res/values/colors.xml +++ b/V2rayNG/app/src/main/res/values/colors.xml @@ -1,10 +1,10 @@ - #607D8B - #455A64 - #CFD8DC + #607D8B + #455A64 + #CFD8DC #9E9E9E - #212121 + #212121 #757575 #FFFFFF #BDBDBD diff --git a/V2rayNG/app/src/main/res/values/dimens.xml b/V2rayNG/app/src/main/res/values/dimens.xml index 19b4e98..455dcef 100644 --- a/V2rayNG/app/src/main/res/values/dimens.xml +++ b/V2rayNG/app/src/main/res/values/dimens.xml @@ -6,5 +6,10 @@ 50dp 24dp 72dp - 60dp + 1dp + + 16dp + 16dp + 8dp + 160dp \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index 589de68..355f5c8 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -4,6 +4,8 @@ Switch Switch First use of this feature, please use the app to activate VPN + Open navigation drawer + Close navigation drawer Stop @@ -15,7 +17,7 @@ Start Services Failure - Server + Configuration file Add config Save config Delete config @@ -23,7 +25,9 @@ Import config from Clipboard Type manually[Vmess] Type manually[Shadowsocks] - Import custom config from locally + custom config + Import custom config from Clipboard + Import custom c from locally Import custom config from URL Import custom config scan URL Confirm delete? @@ -56,12 +60,15 @@ Content There is no data in the clipboard Invalid URL + Ensure inbound has socks proxy port 10808 Loading Select all Enter keywords Bypass Mode + Auto select proxy app + Downloading content @@ -85,11 +92,14 @@ Donate developer Add some experimental advanced features - Remote DNS + Remote DNS(Can not fill) Remote DNS - LAN connection port(0=not allowed) - LAN connection port + SOCKS5 proxy port + SOCKS5 proxy port + + HTTP proxy port(0=not allowed) + HTTP proxy port Speedup domain name resolution Valid only for http/tls @@ -139,6 +149,7 @@ QRcode Export to clipboard + Export full configuration to clipboard @@ -149,8 +160,9 @@ Global - Bypass mainland - + Bypassing the LAN address + Bypass mainland address + Bypassing LAN and mainland address diff --git a/V2rayNG/app/src/main/res/values/styles.xml b/V2rayNG/app/src/main/res/values/styles.xml index 4eeb423..be3498a 100644 --- a/V2rayNG/app/src/main/res/values/styles.xml +++ b/V2rayNG/app/src/main/res/values/styles.xml @@ -2,11 +2,19 @@ + + +