├── .circleci
└── config.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── libs
│ └── README.md
│ └── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ └── io
│ │ └── github
│ │ └── trojan_gfw
│ │ └── igniter
│ │ └── proxy
│ │ └── aidl
│ │ ├── ITrojanService.aidl
│ │ └── ITrojanServiceCallback.aidl
│ ├── cpp
│ ├── CMakeLists.txt
│ └── jni-helper.cpp
│ ├── ic_launcher-web.png
│ ├── java
│ └── io
│ │ └── github
│ │ └── trojan_gfw
│ │ └── igniter
│ │ ├── AboutActivity.java
│ │ ├── ClashHelper.java
│ │ ├── Globals.java
│ │ ├── ILogFunction.java
│ │ ├── IgniterApplication.java
│ │ ├── JNIHelper.java
│ │ ├── LogHelper.java
│ │ ├── MainActivity.java
│ │ ├── PathHelper.java
│ │ ├── ProxyService.java
│ │ ├── TextViewListener.java
│ │ ├── TrojanConfig.java
│ │ ├── TrojanHelper.java
│ │ ├── TrojanURLHelper.java
│ │ ├── common
│ │ ├── app
│ │ │ ├── BaseAppCompatActivity.java
│ │ │ └── BaseFragment.java
│ │ ├── constants
│ │ │ └── Constants.java
│ │ ├── dialog
│ │ │ └── LoadingDialog.java
│ │ ├── mvp
│ │ │ ├── BasePresenter.java
│ │ │ └── BaseView.java
│ │ ├── os
│ │ │ ├── IThreads.java
│ │ │ ├── PreferencesProvider.java
│ │ │ ├── Task.java
│ │ │ └── Threads.java
│ │ └── utils
│ │ │ ├── PermissionUtils.java
│ │ │ ├── PreferenceUtils.java
│ │ │ ├── ProcessUtils.java
│ │ │ └── SnackbarUtils.java
│ │ ├── connection
│ │ ├── TestConnection.java
│ │ └── TrojanConnection.java
│ │ ├── exempt
│ │ ├── activity
│ │ │ └── ExemptAppActivity.java
│ │ ├── adapter
│ │ │ └── AppInfoAdapter.java
│ │ ├── contract
│ │ │ └── ExemptAppContract.java
│ │ ├── data
│ │ │ ├── AppInfo.java
│ │ │ ├── ExemptAppDataManager.java
│ │ │ └── ExemptAppDataSource.java
│ │ ├── fragment
│ │ │ └── ExemptAppFragment.java
│ │ └── presenter
│ │ │ └── ExemptAppPresenter.java
│ │ ├── initializer
│ │ ├── Initializer.java
│ │ ├── InitializerHelper.java
│ │ ├── MainInitializer.java
│ │ ├── ProxyInitializer.java
│ │ └── ToolInitializer.java
│ │ ├── qrcode
│ │ └── ScanQRCodeActivity.java
│ │ ├── servers
│ │ ├── activity
│ │ │ └── ServerListActivity.java
│ │ ├── contract
│ │ │ └── ServerListContract.java
│ │ ├── data
│ │ │ ├── ServerListDataManager.java
│ │ │ └── ServerListDataSource.java
│ │ ├── fragment
│ │ │ ├── ServerListAdapter.java
│ │ │ └── ServerListFragment.java
│ │ └── presenter
│ │ │ └── ServerListPresenter.java
│ │ └── tile
│ │ ├── IgniterTileService.java
│ │ └── ProxyHelper.java
│ └── res
│ ├── drawable-hdpi
│ ├── ic_action_link.png
│ ├── ic_action_name.png
│ ├── ic_save.png
│ ├── ic_search.png
│ ├── ic_tile.png
│ └── qr_code.png
│ ├── drawable-mdpi
│ ├── ic_action_link.png
│ ├── ic_action_name.png
│ ├── ic_save.png
│ ├── ic_search.png
│ ├── ic_tile.png
│ └── qr_code.png
│ ├── drawable-xhdpi
│ ├── ic_action_link.png
│ ├── ic_action_name.png
│ ├── ic_save.png
│ ├── ic_search.png
│ ├── ic_tile.png
│ └── qr_code.png
│ ├── drawable-xxhdpi
│ ├── ic_action_link.png
│ ├── ic_action_name.png
│ ├── ic_save.png
│ ├── ic_search.png
│ ├── ic_tile.png
│ └── qr_code.png
│ ├── drawable-xxxhdpi
│ ├── ic_action_link.png
│ ├── ic_action_name.png
│ ├── ic_save.png
│ ├── ic_search.png
│ ├── ic_tile.png
│ └── qr_code.png
│ ├── drawable
│ ├── common_round_rect_white_bg.xml
│ └── qr_code.png
│ ├── layout
│ ├── activity_about.xml
│ ├── activity_exempt_app.xml
│ ├── activity_main.xml
│ ├── activity_scan_qrcode.xml
│ ├── activity_server_list.xml
│ ├── dialog_loading.xml
│ ├── fragment_exempt_app.xml
│ ├── fragment_server_list.xml
│ ├── item_app_info.xml
│ └── item_server.xml
│ ├── menu
│ ├── menu_exempt_app.xml
│ ├── menu_main.xml
│ └── menu_server_list.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── raw
│ ├── cacert.pem
│ ├── clash_config.yaml
│ └── country.mmdb
│ ├── values
│ ├── arrays.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── root_preferences.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | working_directory: ~/code
5 | docker:
6 | - image: circleci/android:api-28
7 | environment:
8 | JVM_OPTS: -Xmx4G
9 | steps:
10 | - checkout
11 | - run:
12 | name: Pull Submodules
13 | command: git submodule update --init --recursive --remote
14 | - run:
15 | name: Download Go Libs
16 | command: curl -L https://github.com/trojan-gfw/igniter-go-libs/releases/download/1.17/golibs.aar > app/src/libs/golibs.aar
17 | - restore_cache:
18 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
19 | - run:
20 | name: Download Dependencies
21 | command: ./gradlew androidDependencies
22 | - save_cache:
23 | paths:
24 | - ~/.gradle
25 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
26 | - run:
27 | name: Run Tests
28 | command: ./gradlew lint test
29 | - store_test_results:
30 | path: app/build/test-results
31 | destination: test-results/
32 | - run:
33 | name: Initial build
34 | command: ./gradlew clean assembleRelease --no-daemon --stacktrace
35 | - store_artifacts:
36 | path: app/build/outputs/apk/
37 | destination: apks/
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # android lib
12 | *.aar
13 |
14 | # Generated files
15 | bin/
16 | gen/
17 | out/
18 | app/.cxx/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 | release/
24 | debug/
25 |
26 | # Local configuration file (sdk path, etc)
27 | local.properties
28 |
29 | # Proguard folder generated by Eclipse
30 | proguard/
31 |
32 | # Log Files
33 | *.log
34 |
35 | # Android Studio Navigation editor temp files
36 | .navigation/
37 | .project
38 | .settings/
39 |
40 | # Android Studio captures folder
41 | captures/
42 |
43 | # IntelliJ
44 | *.iml
45 | .idea/
46 |
47 | # Keystore files
48 | # Uncomment the following line if you do not want to check your keystore files in.
49 | #*.jks
50 |
51 | # External native build folder generated in Android Studio 2.2 and later
52 | .externalNativeBuild
53 |
54 | # Google Services (e.g. APIs or Firebase)
55 | google-services.json
56 |
57 | # Freeline
58 | freeline.py
59 | freeline/
60 | freeline_project_description.json
61 |
62 | # fastlane
63 | fastlane/report.xml
64 | fastlane/Preview.html
65 | fastlane/screenshots
66 | fastlane/test_output
67 | fastlane/readme.md
68 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "app/src/main/cpp/trojan"]
2 | path = app/src/main/cpp/trojan
3 | url = https://github.com/trojan-gfw/trojan.git
4 | [submodule "app/src/main/cpp/libs"]
5 | path = app/src/main/cpp/libs
6 | url = https://github.com/trojan-gfw/igniter-libs.git
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # igniter
2 |
3 | [](https://circleci.com/gh/trojan-gfw/igniter/tree/master)
4 |
5 |
6 | A trojan client for Android.
7 |
8 |
9 |
10 |
11 |
12 | ## Thanks
13 |
14 | * Dreamacro/clash [GPLv3](https://github.com/Dreamacro/clash/blob/master/LICENSE)
15 | * eycorsican/go-tun2socks [MIT](https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE)
16 | * bingoogolapple/BGAQRCode-Android [Apache License 2.0](https://github.com/bingoogolapple/BGAQRCode-Android)
17 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | ext.versionMajor = 0
4 | ext.versionMinor = 9
5 | ext.versionPatch = 5
6 | ext.versionClassifier = "beta" // or null
7 | ext.isSnapshot = true // set to false when publishing new releases
8 | ext.minimumSdkVersion = 21
9 | ext.targetSdkVersion = 28
10 |
11 | private Integer GenerateVersionCode() {
12 | return ext.minimumSdkVersion * 10000000 + ext.versionMajor * 10000 + ext.versionMinor * 100 + ext.versionPatch
13 | }
14 |
15 | private String GenerateVersionName() {
16 | String versionName = "${ext.versionMajor}.${ext.versionMinor}.${ext.versionPatch}"
17 | if (ext.versionClassifier != null) {
18 | versionName += "-" + ext.versionClassifier
19 | }
20 |
21 | if (ext.isSnapshot) {
22 | versionName += "-" + "SNAPSHOT"
23 | }
24 | return versionName;
25 | }
26 |
27 | android {
28 | ndkVersion "21.1.6352462"
29 | compileSdkVersion 28
30 |
31 | applicationVariants.all { variant ->
32 | variant.resValue "string", "versionName", variant.versionName
33 | }
34 |
35 | defaultConfig {
36 | applicationId "io.github.trojan_gfw.igniter"
37 | minSdkVersion project.ext.minimumSdkVersion
38 | targetSdkVersion project.ext.targetSdkVersion
39 | versionCode GenerateVersionCode()
40 | versionName GenerateVersionName()
41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
42 | ndk {
43 | abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
44 | }
45 | externalNativeBuild {
46 | cmake {
47 | arguments "-DANDROID_CPP_FEATURES=rtti exceptions"
48 | }
49 | }
50 | archivesBaseName = "$applicationId-v$versionName-$versionCode"
51 | }
52 | buildTypes {
53 | debug {
54 | applicationIdSuffix '.debug'
55 | debuggable true
56 | }
57 | release {
58 | minifyEnabled false
59 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
60 | }
61 | }
62 | externalNativeBuild {
63 | cmake {
64 | path file('src/main/cpp/CMakeLists.txt')
65 | }
66 | }
67 | }
68 |
69 | repositories {
70 | flatDir {
71 | dirs 'src/libs'
72 | }
73 | }
74 |
75 | dependencies {
76 | implementation 'com.google.android.material:material:1.2.0-alpha06'
77 | implementation 'androidx.appcompat:appcompat:1.2.0-beta01'
78 | implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'
79 | implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
80 | implementation 'androidx.core:core:1.3.0-rc01'
81 | implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.7'
82 | implementation 'androidx.preference:preference:1.1.1'
83 | testImplementation 'junit:junit:4.13'
84 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
85 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
86 |
87 | api(name: 'golibs', ext: 'aar')
88 | }
89 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/libs/README.md:
--------------------------------------------------------------------------------
1 | put golibs.aar here.
2 |
3 | ref:
4 | - https://github.com/trojan-gfw/igniter-go-libs
5 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
20 |
25 |
28 |
31 |
32 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/aidl/io/github/trojan_gfw/igniter/proxy/aidl/ITrojanService.aidl:
--------------------------------------------------------------------------------
1 | // ITrojanService.aidl
2 | package io.github.trojan_gfw.igniter.proxy.aidl;
3 | import io.github.trojan_gfw.igniter.proxy.aidl.ITrojanServiceCallback;
4 | // Declare any non-default types here with import statements
5 |
6 | interface ITrojanService {
7 | int getState();
8 | void testConnection(String testUrl);
9 | void showDevelopInfoInLogcat();
10 | oneway void registerCallback(in ITrojanServiceCallback callback);
11 | oneway void unregisterCallback(in ITrojanServiceCallback callback);
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/aidl/io/github/trojan_gfw/igniter/proxy/aidl/ITrojanServiceCallback.aidl:
--------------------------------------------------------------------------------
1 | // ITrojanServiceCallback.aidl
2 | package io.github.trojan_gfw.igniter.proxy.aidl;
3 |
4 | // Declare any non-default types here with import statements
5 |
6 | interface ITrojanServiceCallback {
7 | void onStateChanged(int state, String msg);
8 | void onTestResult(String testUrl, boolean connected, long delay, String error);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.6)
2 | project(igniter)
3 | add_definitions(-DENABLE_ANDROID_LOG)
4 | # As long as we use OpenSSL 1.1.1, we are safe
5 | add_definitions(-DENABLE_TLS13_CIPHERSUITES)
6 | find_library( # Sets the name of the path variable.
7 | androidLogLib
8 |
9 | # Specifies the name of the NDK library that
10 | # you want CMake to locate.
11 | log)
12 | add_library(trojan
13 | trojan/src/core/authenticator.cpp
14 | trojan/src/core/config.cpp
15 | trojan/src/core/log.cpp
16 | trojan/src/core/service.cpp
17 | trojan/src/core/version.cpp
18 | trojan/src/proto/socks5address.cpp
19 | trojan/src/proto/trojanrequest.cpp
20 | trojan/src/proto/udppacket.cpp
21 | trojan/src/session/clientsession.cpp
22 | trojan/src/session/forwardsession.cpp
23 | trojan/src/session/natsession.cpp
24 | trojan/src/session/serversession.cpp
25 | trojan/src/session/session.cpp
26 | trojan/src/session/udpforwardsession.cpp
27 | trojan/src/ssl/ssldefaults.cpp
28 | trojan/src/ssl/sslsession.cpp)
29 | target_include_directories(trojan PRIVATE
30 | ${CMAKE_SOURCE_DIR}/libs/include
31 | ${CMAKE_SOURCE_DIR}/libs/include/${ANDROID_ABI}
32 | ${CMAKE_SOURCE_DIR}/trojan/src)
33 | target_link_libraries(trojan
34 | ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libboost_program_options.a
35 | ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libboost_system.a
36 | ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libssl.a
37 | ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libcrypto.a
38 | ${androidLogLib})
39 | add_library(jni-helper SHARED jni-helper.cpp)
40 | target_include_directories(jni-helper PRIVATE
41 | ${CMAKE_SOURCE_DIR}/trojan/src
42 | ${CMAKE_SOURCE_DIR}/libn2t/src
43 | ${CMAKE_SOURCE_DIR}/libs/include
44 | ${CMAKE_SOURCE_DIR}/libs/include/${ANDROID_ABI})
45 | target_link_libraries(jni-helper trojan)
46 |
--------------------------------------------------------------------------------
/app/src/main/cpp/jni-helper.cpp:
--------------------------------------------------------------------------------
1 | #include "jni.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | using namespace std;
8 |
9 |
10 | static thread *trojanThread = nullptr;
11 | static Config *trojanConfig = nullptr;
12 | static Service *trojanService = nullptr;
13 |
14 |
15 | static void startTrojan(const string &config)
16 | {
17 | trojanConfig = new Config();
18 | trojanConfig->load(config);
19 | trojanService = new Service(*trojanConfig);
20 | trojanService->run();
21 | }
22 |
23 |
24 |
25 | extern "C" {
26 | JNIEXPORT void JNICALL Java_io_github_trojan_1gfw_igniter_JNIHelper_trojan(JNIEnv *env, jclass, jstring config) {
27 | if (trojanThread != nullptr)
28 | return;
29 | const char *s = env->GetStringUTFChars(config, 0);
30 | string a(s);
31 | env->ReleaseStringUTFChars(config, s);
32 | trojanThread = new thread(startTrojan, a);
33 | }
34 |
35 |
36 | JNIEXPORT void JNICALL Java_io_github_trojan_1gfw_igniter_JNIHelper_stop(JNIEnv *env, jclass) {
37 |
38 | if (trojanThread != nullptr) {
39 | trojanService->stop();
40 | trojanThread->join();
41 | delete trojanService;
42 | delete trojanConfig;
43 | delete trojanThread;
44 | trojanThread = nullptr;
45 | }
46 | }
47 | }
48 |
49 | jint JNI_OnLoad(JavaVM* vm, void* reserved)
50 | {
51 | return JNI_VERSION_1_6;
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 | import androidx.preference.PreferenceFragmentCompat;
9 |
10 | public class AboutActivity extends AppCompatActivity {
11 |
12 | public static Intent create(Context context) {
13 | return new Intent(context, AboutActivity.class);
14 | }
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_about);
20 | getSupportFragmentManager()
21 | .beginTransaction()
22 | .replace(R.id.settings, new SettingsFragment())
23 | .commit();
24 | }
25 |
26 | public static class SettingsFragment extends PreferenceFragmentCompat {
27 | @Override
28 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
29 | setPreferencesFromResource(R.xml.root_preferences, rootKey);
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/ClashHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileOutputStream;
6 | import java.util.regex.Matcher;
7 | import java.util.regex.Pattern;
8 |
9 | public class ClashHelper {
10 |
11 | private static final String TAG = "ClashConfig";
12 |
13 | // In general, we capture only one group for replacement
14 | public static final Pattern clashSocksPortPattern = Pattern.compile("^socks.*port:\\s+(\\d+)", Pattern.MULTILINE);
15 | public static final Pattern trojanPortPattern = Pattern.compile("trojan.*socks.*port:\\s+(\\d+)", Pattern.MULTILINE);
16 |
17 | /*
18 | * Generate Clash running configuration file according to the template file
19 | */
20 | public static void ChangeClashConfig(String clashConfigPath, long trojanPort, long clashSocksPort) {
21 | File tmpClashConfigFile = new File(clashConfigPath + ".tmp");
22 | File clashConfigFile = new File(clashConfigPath);
23 | try {
24 | String str;
25 | try (FileInputStream fis = new FileInputStream(clashConfigFile)) {
26 | long origClashConfigLen = clashConfigFile.length();
27 | byte[] content = new byte[(int) origClashConfigLen];
28 | if (fis.read(content) != origClashConfigLen) {
29 | LogHelper.e(TAG, "fail to read full content of clash config file");
30 | }
31 | str = new String(content);
32 | }
33 |
34 | String clashSocksPortStr = String.valueOf(clashSocksPort);
35 | String trojanPortStr = String.valueOf(trojanPort);
36 | str = replaceGroup(clashSocksPortPattern, str, 1, clashSocksPortStr);
37 | str = replaceGroup(trojanPortPattern, str, 1, trojanPortStr);
38 |
39 | try (FileOutputStream fos = new FileOutputStream(tmpClashConfigFile)) {
40 | fos.write(str.getBytes());
41 | }
42 |
43 | if (!clashConfigFile.delete()) {
44 | LogHelper.e(TAG, "fail to delete old clash config file");
45 | }
46 | if (!tmpClashConfigFile.renameTo(clashConfigFile)) {
47 | LogHelper.e(TAG, "fail to rename tmp clash config file");
48 | }
49 |
50 | } catch (Exception e) {
51 | e.printStackTrace();
52 | }
53 | }
54 |
55 | public static void ShowConfig(String clashConfigPath) {
56 | File file = new File(clashConfigPath);
57 |
58 | try {
59 | try (FileInputStream fis = new FileInputStream(file)) {
60 | StringBuilder sb = new StringBuilder();
61 | byte[] content = new byte[(int) file.length()];
62 | fis.read(content);
63 | sb.append("\r\n");
64 | sb.append(new String(content));
65 | LogHelper.v(TAG, sb.toString());
66 | }
67 | } catch (Exception e) {
68 | e.printStackTrace();
69 | }
70 | }
71 |
72 | public static String replaceGroup(Pattern regex, String source,
73 | int groupToReplace, String replacement) throws Exception {
74 | return replaceGroup(regex, source, groupToReplace, 1, replacement);
75 | }
76 |
77 | public static String replaceGroup(Pattern regex, String source,
78 | int groupToReplace, int groupOccurrence, String replacement) throws Exception {
79 | Matcher m = regex.matcher(source);
80 | for (int i = 0; i < groupOccurrence; i++) {
81 | if (!m.find())
82 | throw new Exception("Pattern not found"); // pattern not met, may also throw an exception here
83 | }
84 | return new StringBuilder(source)
85 | .replace(m.start(groupToReplace), m.end(groupToReplace), replacement)
86 | .toString();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/Globals.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 |
6 | import java.io.File;
7 |
8 | public class Globals {
9 |
10 | private static String cacheDir;
11 | private static String filesDir;
12 | private static String externalFilesDir;
13 | private static TrojanConfig trojanConfigInstance;
14 |
15 | public static void Init(Context ctx) {
16 | cacheDir = ctx.getCacheDir().getAbsolutePath();
17 | filesDir = ctx.getFilesDir().getAbsolutePath();
18 | File externalDocDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
19 | File igniterExternalFileDir = new File(externalDocDir, "igniter");
20 | externalFilesDir = igniterExternalFileDir.getAbsolutePath();
21 | trojanConfigInstance = new TrojanConfig();
22 | trojanConfigInstance.setCaCertPath(Globals.getCaCertPath());
23 | }
24 |
25 | public static String getCaCertPath() {
26 | return PathHelper.combine(cacheDir, "cacert.pem");
27 | }
28 |
29 | public static String getCountryMmdbPath() {
30 | return PathHelper.combine(filesDir, "Country.mmdb");
31 | }
32 |
33 | public static String getClashConfigPath() {
34 | return PathHelper.combine(filesDir, "config.yaml");
35 | }
36 |
37 | public static String getTrojanConfigPath() {
38 | return PathHelper.combine(filesDir, "config.json");
39 | }
40 |
41 | public static String getTrojanConfigListPath() {
42 | return PathHelper.combine(filesDir, "config_list.json");
43 | }
44 |
45 | public static String getPreferencesFilePath() {
46 | return PathHelper.combine(filesDir, "preferences.txt");
47 | }
48 |
49 | public static String getExemptedAppListPath() {
50 | return PathHelper.combine(externalFilesDir, "exempted_app_list.txt");
51 | }
52 |
53 | public static void setTrojanConfigInstance(TrojanConfig config) {
54 | trojanConfigInstance = config;
55 | }
56 |
57 | public static TrojanConfig getTrojanConfigInstance() {
58 | return trojanConfigInstance;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/ILogFunction.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | public interface ILogFunction {
4 | public int output(String tag, String msg);
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/IgniterApplication.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 |
6 | import io.github.trojan_gfw.igniter.initializer.InitializerHelper;
7 |
8 | public class IgniterApplication extends Application {
9 | @Override
10 | protected void attachBaseContext(Context base) {
11 | super.attachBaseContext(base);
12 | InitializerHelper.runInit(this);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/JNIHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | public class JNIHelper {
4 | static {
5 | System.loadLibrary("jni-helper");
6 | }
7 |
8 | public static native void trojan(String config);
9 |
10 | public static native void stop();
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/LogHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import android.util.Log;
4 |
5 | public final class LogHelper {
6 |
7 | // Logcat is line-buffered
8 |
9 | private static final int maxLogSize = 1000;
10 |
11 | private static final ILogFunction _v = new ILogFunction() {
12 | @Override
13 | public int output(String tag, String msg) {
14 | return Log.v(tag, msg);
15 | }
16 | };
17 | private static final ILogFunction _d = new ILogFunction() {
18 | @Override
19 | public int output(String tag, String msg) {
20 | return Log.d(tag, msg);
21 | }
22 | };
23 |
24 | private static final ILogFunction _i = new ILogFunction() {
25 | @Override
26 | public int output(String tag, String msg) {
27 | return Log.i(tag, msg);
28 | }
29 | };
30 |
31 | private static final ILogFunction _w = new ILogFunction() {
32 | @Override
33 | public int output(String tag, String msg) {
34 | return Log.w(tag, msg);
35 | }
36 | };
37 |
38 | private static final ILogFunction _e = new ILogFunction() {
39 | @Override
40 | public int output(String tag, String msg) {
41 | return Log.e(tag, msg);
42 | }
43 | };
44 |
45 | private static void UnderlyingLog(String veryLongString, ILogFunction func, String TAG) {
46 |
47 | if (veryLongString.length() < maxLogSize) {
48 | func.output(TAG, veryLongString);
49 | return;
50 | }
51 |
52 | for (int i = 0; i <= veryLongString.length() / maxLogSize; i++) {
53 | int start = i * maxLogSize;
54 | int end = (i + 1) * maxLogSize;
55 | end = Math.min(end, veryLongString.length());
56 | func.output(TAG, veryLongString.substring(start, end));
57 | }
58 | }
59 |
60 | public static void v(String tag, String msg) {
61 | UnderlyingLog(msg, _v, tag);
62 | }
63 |
64 | public static void d(String tag, String msg) {
65 | UnderlyingLog(msg, _d, tag);
66 | }
67 |
68 | public static void i(String tag, String msg) {
69 | UnderlyingLog(msg, _i, tag);
70 | }
71 |
72 | public static void w(String tag, String msg) {
73 | UnderlyingLog(msg, _w, tag);
74 | }
75 |
76 | public static void e(String tag, String msg) {
77 | UnderlyingLog(msg, _e, tag);
78 | }
79 |
80 | public static void showDevelopInfoInLogcat() {
81 | util.Util.logGoRoutineCount();
82 | util.Util.logGoroutineStackTrace();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/PathHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import java.io.File;
4 |
5 | public class PathHelper {
6 | public static String combine(String... paths) {
7 | File file = new File(paths[0]);
8 |
9 | for (int i = 1; i < paths.length; i++) {
10 | file = new File(file, paths[i]);
11 | }
12 |
13 | return file.getPath();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/TextViewListener.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import android.text.Editable;
4 | import android.text.TextWatcher;
5 |
6 |
7 | /**
8 | * Text view listener which splits the update text event in four parts:
9 | *
10 | * - The text placed before the updated part.
11 | * - The old text in the updated part.
12 | * - The new text in the updated part.
13 | * - The text placed after the updated part.
14 | *
15 | *
16 | * myEditText.addTextChangedListener(new TextViewListener() {
17 | * \@Override
18 | * protected void onTextChanged(String before, String old, String aNew, String after) {
19 | * // intuitive usation of parametters
20 | * String completeOldText = before + old + after;
21 | * String completeNewText = before + aNew + after;
22 | *
23 | * // update TextView
24 | * startUpdates(); // to prevent infinite loop.
25 | * myEditText.setText(myNewText);
26 | * endUpdates();
27 | * }
28 | * }
29 | *
30 | * Created by Jeremy B.
31 | */
32 |
33 | public abstract class TextViewListener implements TextWatcher {
34 | /**
35 | * Unchanged sequence which is placed before the updated sequence.
36 | */
37 | private String _before;
38 |
39 | /**
40 | * Updated sequence before the update.
41 | */
42 | private String _old;
43 |
44 | /**
45 | * Updated sequence after the update.
46 | */
47 | private String _new;
48 |
49 | /**
50 | * Unchanged sequence which is placed after the updated sequence.
51 | */
52 | private String _after;
53 |
54 | /**
55 | * Indicates when changes are made from within the listener, should be omitted.
56 | */
57 | private boolean _ignore = false;
58 |
59 | @Override
60 | public void beforeTextChanged(CharSequence sequence, int start, int count, int after) {
61 | _before = sequence.subSequence(0,start).toString();
62 | _old = sequence.subSequence(start, start+count).toString();
63 | _after = sequence.subSequence(start+count, sequence.length()).toString();
64 | }
65 |
66 | @Override
67 | public void onTextChanged(CharSequence sequence, int start, int before, int count) {
68 | _new = sequence.subSequence(start, start+count).toString();
69 | }
70 |
71 | @Override
72 | public void afterTextChanged(Editable sequence) {
73 | if (_ignore)
74 | return;
75 |
76 | onTextChanged(_before, _old, _new, _after);
77 | }
78 |
79 | /**
80 | * Triggered method when the text in the text view has changed.
81 | *
82 | * You can apply changes to the text view from this method
83 | * with the condition to call {@link #startUpdates()} before any update,
84 | * and to call {@link #endUpdates()} after them.
85 | *
86 | * @param before Unchanged part of the text placed before the updated part.
87 | * @param old Old updated part of the text.
88 | * @param aNew New updated part of the text?
89 | * @param after Unchanged part of the text placed after the updated part.
90 | */
91 | protected abstract void onTextChanged(String before, String old, String aNew, String after);
92 |
93 | /**
94 | * Call this method when you start to update the text view, so it stops listening to it and then prevent an infinite loop.
95 | * @see #endUpdates()
96 | */
97 | protected void startUpdates(){
98 | _ignore = true;
99 | }
100 |
101 | /**
102 | * Call this method when you finished to update the text view in order to restart to listen to it.
103 | * @see #startUpdates()
104 | */
105 | protected void endUpdates(){
106 | _ignore = false;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/TrojanConfig.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 | import android.text.TextUtils;
6 |
7 | import androidx.annotation.Nullable;
8 |
9 | import org.json.JSONArray;
10 | import org.json.JSONObject;
11 |
12 | public class TrojanConfig implements Parcelable {
13 |
14 | private String localAddr;
15 | private int localPort;
16 | private String remoteAddr;
17 | private int remotePort;
18 | private String password;
19 | private boolean verifyCert;
20 | private String caCertPath;
21 | private boolean enableIpv6;
22 | private String cipherList;
23 | private String tls13CipherList;
24 |
25 |
26 | public TrojanConfig() {
27 | // defaults
28 | this.localAddr = "127.0.0.1";
29 | this.localPort = 1081;
30 | this.remotePort = 443;
31 | this.verifyCert = true;
32 | this.cipherList = "ECDHE-ECDSA-AES128-GCM-SHA256:"
33 | + "ECDHE-RSA-AES128-GCM-SHA256:"
34 | + "ECDHE-ECDSA-CHACHA20-POLY1305:"
35 | + "ECDHE-RSA-CHACHA20-POLY1305:"
36 | + "ECDHE-ECDSA-AES256-GCM-SHA384:"
37 | + "ECDHE-RSA-AES256-GCM-SHA384:"
38 | + "ECDHE-ECDSA-AES256-SHA:"
39 | + "ECDHE-ECDSA-AES128-SHA:"
40 | + "ECDHE-RSA-AES128-SHA:"
41 | + "ECDHE-RSA-AES256-SHA:"
42 | + "DHE-RSA-AES128-SHA:"
43 | + "DHE-RSA-AES256-SHA:"
44 | + "AES128-SHA:"
45 | + "AES256-SHA:"
46 | + "DES-CBC3-SHA";
47 | this.tls13CipherList = "TLS_AES_128_GCM_SHA256:"
48 | + "TLS_CHACHA20_POLY1305_SHA256:"
49 | + "TLS_AES_256_GCM_SHA384";
50 | }
51 |
52 | protected TrojanConfig(Parcel in) {
53 | localAddr = in.readString();
54 | localPort = in.readInt();
55 | remoteAddr = in.readString();
56 | remotePort = in.readInt();
57 | password = in.readString();
58 | verifyCert = in.readByte() != 0;
59 | caCertPath = in.readString();
60 | enableIpv6 = in.readByte() != 0;
61 | cipherList = in.readString();
62 | tls13CipherList = in.readString();
63 | }
64 |
65 | public static final Creator CREATOR = new Creator() {
66 | @Override
67 | public TrojanConfig createFromParcel(Parcel in) {
68 | return new TrojanConfig(in);
69 | }
70 |
71 | @Override
72 | public TrojanConfig[] newArray(int size) {
73 | return new TrojanConfig[size];
74 | }
75 | };
76 |
77 | public String generateTrojanConfigJSON() {
78 | try {
79 | return new JSONObject()
80 | .put("local_addr", this.localAddr)
81 | .put("local_port", this.localPort)
82 | .put("remote_addr", this.remoteAddr)
83 | .put("remote_port", this.remotePort)
84 | .put("password", new JSONArray().put(password))
85 | .put("log_level", 2) // WARN
86 | .put("ssl", new JSONObject()
87 | .put("verify", this.verifyCert)
88 | .put("cert", this.caCertPath)
89 | .put("cipher", this.cipherList)
90 | .put("cipher_tls13", this.tls13CipherList)
91 | .put("alpn", new JSONArray().put("h2").put("http/1.1")))
92 | .put("enable_ipv6", this.enableIpv6)
93 | .toString();
94 | } catch (Exception e) {
95 | e.printStackTrace();
96 | return null;
97 | }
98 | }
99 |
100 | public void fromJSON(String jsonStr) {
101 | try {
102 | JSONObject json = new JSONObject(jsonStr);
103 | this.setLocalAddr(json.getString("local_addr"))
104 | .setLocalPort(json.getInt("local_port"))
105 | .setRemoteAddr(json.getString("remote_addr"))
106 | .setRemotePort(json.getInt("remote_port"))
107 | .setPassword(json.getJSONArray("password").getString(0))
108 | .setEnableIpv6(json.getBoolean("enable_ipv6"))
109 | .setVerifyCert(json.getJSONObject("ssl").getBoolean("verify"));
110 |
111 | } catch (Exception e) {
112 | e.printStackTrace();
113 | }
114 | }
115 |
116 | public void copyFrom(TrojanConfig that) {
117 | this
118 | .setLocalAddr(that.localAddr)
119 | .setLocalPort(that.localPort)
120 | .setRemoteAddr(that.remoteAddr)
121 | .setRemotePort(that.remotePort)
122 | .setPassword(that.password)
123 | .setEnableIpv6(that.enableIpv6)
124 | .setVerifyCert(that.verifyCert)
125 | .setCaCertPath(that.caCertPath)
126 | .setCipherList(that.cipherList)
127 | .setTls13CipherList(that.tls13CipherList);
128 |
129 | }
130 |
131 | public boolean isValidRunningConfig() {
132 | return !TextUtils.isEmpty(this.caCertPath)
133 | && !TextUtils.isEmpty(this.remoteAddr)
134 | && !TextUtils.isEmpty(this.password);
135 | }
136 |
137 | public String getLocalAddr() {
138 | return localAddr;
139 | }
140 |
141 | public TrojanConfig setLocalAddr(String localAddr) {
142 | this.localAddr = localAddr;
143 | return this;
144 | }
145 |
146 | public int getLocalPort() {
147 | return localPort;
148 | }
149 |
150 | public TrojanConfig setLocalPort(int localPort) {
151 | this.localPort = localPort;
152 | return this;
153 | }
154 |
155 | public String getRemoteAddr() {
156 | return remoteAddr;
157 | }
158 |
159 | public TrojanConfig setRemoteAddr(String remoteAddr) {
160 | this.remoteAddr = remoteAddr;
161 | return this;
162 | }
163 |
164 | public int getRemotePort() {
165 | return remotePort;
166 | }
167 |
168 | public TrojanConfig setRemotePort(int remotePort) {
169 | this.remotePort = remotePort;
170 | return this;
171 | }
172 |
173 | public String getPassword() {
174 | return password;
175 | }
176 |
177 | public TrojanConfig setPassword(String password) {
178 | this.password = password;
179 | return this;
180 | }
181 |
182 | public boolean getVerifyCert() {
183 | return verifyCert;
184 | }
185 |
186 | public TrojanConfig setVerifyCert(boolean verifyCert) {
187 | this.verifyCert = verifyCert;
188 | return this;
189 | }
190 |
191 | public String getCaCertPath() {
192 | return caCertPath;
193 | }
194 |
195 | public TrojanConfig setCaCertPath(String caCertPath) {
196 | this.caCertPath = caCertPath;
197 | return this;
198 | }
199 |
200 | public boolean getEnableIpv6() {
201 | return enableIpv6;
202 | }
203 |
204 | public TrojanConfig setEnableIpv6(boolean enableIpv6) {
205 | this.enableIpv6 = enableIpv6;
206 | return this;
207 | }
208 |
209 | public String getCipherList() {
210 | return cipherList;
211 | }
212 |
213 | public TrojanConfig setCipherList(String cipherList) {
214 | this.cipherList = cipherList;
215 | return this;
216 | }
217 |
218 | public String getTls13CipherList() {
219 | return tls13CipherList;
220 | }
221 |
222 | public TrojanConfig setTls13CipherList(String tls13CipherList) {
223 | this.tls13CipherList = tls13CipherList;
224 | return this;
225 | }
226 |
227 | @Override
228 | public boolean equals(@Nullable Object obj) {
229 | if (!(obj instanceof TrojanConfig)) {
230 | return false;
231 | }
232 | TrojanConfig that = (TrojanConfig) obj;
233 | return (paramEquals(remoteAddr, that.remoteAddr) && paramEquals(remotePort, that.remotePort)
234 | && paramEquals(localAddr, that.localAddr) && paramEquals(localPort, that.localPort))
235 | && paramEquals(password, that.password) && paramEquals(verifyCert, that.verifyCert)
236 | && paramEquals(caCertPath, that.caCertPath) && paramEquals(enableIpv6, that.enableIpv6)
237 | && paramEquals(cipherList, that.cipherList) && paramEquals(tls13CipherList, that.tls13CipherList);
238 | }
239 |
240 | private static boolean paramEquals(Object a, Object b) {
241 | if (a == b) {
242 | return true;
243 | }
244 | if (a == null || b == null) {
245 | return false;
246 | }
247 | return a.equals(b);
248 | }
249 |
250 | @Override
251 | public int describeContents() {
252 | return 0;
253 | }
254 |
255 | @Override
256 | public void writeToParcel(Parcel dest, int flags) {
257 | dest.writeString(localAddr);
258 | dest.writeInt(localPort);
259 | dest.writeString(remoteAddr);
260 | dest.writeInt(remotePort);
261 | dest.writeString(password);
262 | dest.writeByte((byte) (verifyCert ? 1 : 0));
263 | dest.writeString(caCertPath);
264 | dest.writeByte((byte) (enableIpv6 ? 1 : 0));
265 | dest.writeString(cipherList);
266 | dest.writeString(tls13CipherList);
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/TrojanHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.annotation.Nullable;
5 |
6 | import org.json.JSONArray;
7 | import org.json.JSONException;
8 | import org.json.JSONObject;
9 |
10 | import java.io.BufferedReader;
11 | import java.io.File;
12 | import java.io.FileInputStream;
13 | import java.io.FileOutputStream;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.InputStreamReader;
17 | import java.io.OutputStream;
18 | import java.util.ArrayList;
19 | import java.util.Collections;
20 | import java.util.List;
21 |
22 | public class TrojanHelper {
23 | private static final String SINGLE_CONFIG_TAG = "TrojanConfig";
24 | private static final String CONFIG_LIST_TAG = "TrojanConfigList";
25 |
26 | public static boolean writeTrojanServerConfigList(List configList, String trojanConfigListPath) {
27 | JSONArray jsonArray = new JSONArray();
28 | for (TrojanConfig config : configList) {
29 | try {
30 | JSONObject jsonObject = new JSONObject(config.generateTrojanConfigJSON());
31 | jsonArray.put(jsonObject);
32 | } catch (JSONException e) {
33 | e.printStackTrace();
34 | return false;
35 | }
36 | }
37 | String configStr = jsonArray.toString();
38 | File file = new File(trojanConfigListPath);
39 | if (file.exists()) {
40 | file.delete();
41 | }
42 | try (OutputStream fos = new FileOutputStream(file)) {
43 | fos.write(configStr.getBytes());
44 | fos.flush();
45 | } catch (IOException e) {
46 | e.printStackTrace();
47 | return false;
48 | }
49 | return true;
50 | }
51 |
52 | @NonNull
53 | public static List readTrojanServerConfigList(String trojanConfigListPath) {
54 | File file = new File(trojanConfigListPath);
55 | if (!file.exists()) {
56 | return Collections.emptyList();
57 | }
58 | try (InputStream fis = new FileInputStream(file)) {
59 | byte[] data = new byte[(int) file.length()];
60 | fis.read(data);
61 | String json = new String(data);
62 | JSONArray jsonArr = new JSONArray(json);
63 | int len = jsonArr.length();
64 | List list = new ArrayList<>(len);
65 | for (int i = 0; i < len; i++) {
66 | list.add(parseTrojanConfigFromJSON(jsonArr.getJSONObject(i).toString()));
67 | }
68 | return list;
69 | } catch (IOException | JSONException e) {
70 | e.printStackTrace();
71 | }
72 | return Collections.emptyList();
73 | }
74 |
75 | public static void ShowTrojanConfigList(String trojanConfigListPath) {
76 | File file = new File(trojanConfigListPath);
77 |
78 | try {
79 | try (FileInputStream fis = new FileInputStream(file)) {
80 | byte[] content = new byte[(int) file.length()];
81 | fis.read(content);
82 | LogHelper.v(CONFIG_LIST_TAG, new String(content));
83 | }
84 | } catch (Exception e) {
85 | e.printStackTrace();
86 | }
87 | }
88 |
89 | private static String parseTrojanConfigToJSON(TrojanConfig config) {
90 | try {
91 | /*JSONObject json = new JSONObject();
92 | json.put("local_addr", config.getLocalAddr());
93 | json.put("local_port", config.getLocalPort());
94 | json.put("remote_addr", config.getRemoteAddr());
95 | json.put("remote_port", config.getRemotePort());
96 | json.put("password", config.getPassword());
97 | json.put("verify_cert", config.getVerifyCert());
98 | json.put("ca_cert_path", config.getCaCertPath());
99 | json.put("enable_ipv6", config.getEnableIpv6());
100 | json.put("cipher_list", config.getCipherList());
101 | json.put("tls13_cipher_list", config.getTls13CipherList());
102 | return json.toString();*/
103 | return config.generateTrojanConfigJSON();
104 | } catch (Exception e) {
105 | e.printStackTrace();
106 | }
107 | return "";
108 | }
109 |
110 | @Nullable
111 | public static TrojanConfig readTrojanConfig(String trojanConfigPath) {
112 | File file = new File(trojanConfigPath);
113 | if (!file.exists()) {
114 | return null;
115 | }
116 | StringBuilder sb = new StringBuilder();
117 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
118 | String line;
119 | while ((line = reader.readLine()) != null) {
120 | sb.append(line);
121 | }
122 | TrojanConfig trojanConfig = new TrojanConfig();
123 | trojanConfig.fromJSON(sb.toString());
124 | return trojanConfig;
125 | } catch (IOException e) {
126 | e.printStackTrace();
127 | }
128 | return null;
129 | }
130 |
131 | @NonNull
132 | private static TrojanConfig parseTrojanConfigFromJSON(String json) {
133 | TrojanConfig config = new TrojanConfig();
134 | config.fromJSON(json);
135 | return config;
136 | }
137 |
138 | public static void WriteTrojanConfig(TrojanConfig trojanConfig, String trojanConfigPath) {
139 | String config = trojanConfig.generateTrojanConfigJSON();
140 | File file = new File(trojanConfigPath);
141 | try {
142 | try (FileOutputStream fos = new FileOutputStream(file)) {
143 | fos.write(config.getBytes());
144 | }
145 | } catch (Exception e) {
146 | e.printStackTrace();
147 | }
148 | }
149 |
150 | public static void ChangeListenPort(String trojanConfigPath, long port) {
151 | File file = new File(trojanConfigPath);
152 | if (file.exists()) {
153 | try {
154 | String str;
155 | try (FileInputStream fis = new FileInputStream(file)) {
156 | byte[] content = new byte[(int) file.length()];
157 | fis.read(content);
158 | str = new String(content);
159 |
160 | }
161 | JSONObject json = new JSONObject(str);
162 | json.put("local_port", port);
163 | try (FileOutputStream fos = new FileOutputStream(file)) {
164 |
165 | fos.write(json.toString().getBytes());
166 | }
167 | } catch (Exception e) {
168 | e.printStackTrace();
169 | }
170 | }
171 | }
172 |
173 | public static void ShowConfig(String trojanConfigPath) {
174 | File file = new File(trojanConfigPath);
175 |
176 | try {
177 | try (FileInputStream fis = new FileInputStream(file)) {
178 | byte[] content = new byte[(int) file.length()];
179 | fis.read(content);
180 | LogHelper.v(SINGLE_CONFIG_TAG, new String(content));
181 | }
182 | } catch (Exception e) {
183 | e.printStackTrace();
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/TrojanURLHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter;
2 |
3 | import java.net.URI;
4 |
5 | public class TrojanURLHelper {
6 | public static String GenerateTrojanURL(TrojanConfig trojanConfig) {
7 |
8 | URI trojanUri;
9 | try {
10 | trojanUri = new URI("trojan",
11 | trojanConfig.getPassword(),
12 | trojanConfig.getRemoteAddr(),
13 | trojanConfig.getRemotePort(),
14 | null, null, null);
15 | } catch (java.net.URISyntaxException e) {
16 | e.printStackTrace();
17 | return null;
18 | }
19 |
20 | return trojanUri.toString();
21 | }
22 |
23 | public static TrojanConfig ParseTrojanURL(String trojanURLStr) {
24 | URI trojanUri;
25 | try {
26 | trojanUri = new URI(trojanURLStr);
27 | } catch (java.net.URISyntaxException e) {
28 | e.printStackTrace();
29 | return null;
30 | }
31 | String scheme = trojanUri.getScheme();
32 | if (scheme == null) {
33 | return null;
34 | }
35 | if (!scheme.equals("trojan"))
36 | return null;
37 | String host = trojanUri.getHost();
38 | int port = trojanUri.getPort();
39 | String userInfo = trojanUri.getUserInfo();
40 |
41 | TrojanConfig retConfig = new TrojanConfig();
42 | retConfig.setRemoteAddr(host);
43 | retConfig.setRemotePort(port);
44 | retConfig.setPassword(userInfo);
45 | return retConfig;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/app/BaseAppCompatActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.app;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 |
6 | import androidx.annotation.Nullable;
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | public abstract class BaseAppCompatActivity extends AppCompatActivity {
10 | protected Context mContext;
11 |
12 | @Override
13 | protected void onCreate(@Nullable Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | mContext = this;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/app/BaseFragment.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.app;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | import androidx.annotation.IdRes;
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 | import androidx.fragment.app.Fragment;
11 | import androidx.fragment.app.FragmentActivity;
12 |
13 | public abstract class BaseFragment extends Fragment {
14 | protected View mRootView;
15 | protected Context mContext;
16 |
17 | @Override
18 | public void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setRetainInstance(true);
21 | }
22 |
23 | @Override
24 | public void onAttach(Context context) {
25 | super.onAttach(context);
26 | mContext = context;
27 | }
28 |
29 | @Override
30 | public void onDetach() {
31 | super.onDetach();
32 | mContext = null;
33 | }
34 |
35 | @Override
36 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
37 | super.onViewCreated(view, savedInstanceState);
38 | mRootView = view;
39 | }
40 |
41 | protected T findViewById(@IdRes int id) {
42 | return mRootView.findViewById(id);
43 | }
44 |
45 | protected void runOnUiThread(Runnable runnable) {
46 | FragmentActivity activity = getActivity();
47 | if (activity != null) {
48 | activity.runOnUiThread(runnable);
49 | }
50 | }
51 |
52 | protected void finishActivity() {
53 | FragmentActivity activity = getActivity();
54 | if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
55 | activity.finish();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/constants/Constants.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.constants;
2 |
3 | public abstract class Constants {
4 | public static final String PREFERENCE_AUTHORITY = "io.github.trojan_gfw.igniter";
5 | public static final String PREFERENCE_PATH = "preferences";
6 | public static final String PREFERENCE_URI = "content://" + PREFERENCE_AUTHORITY + "/" + PREFERENCE_PATH;
7 | public static final String PREFERENCE_KEY_ENABLE_CLASH = "enable_clash";
8 | public static final String PREFERENCE_KEY_FIRST_START = "first_start";
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/dialog/LoadingDialog.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.dialog;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.graphics.Color;
6 | import android.graphics.PorterDuff;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.view.Window;
9 | import android.widget.TextView;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.core.content.ContextCompat;
13 | import androidx.core.widget.ContentLoadingProgressBar;
14 |
15 | import io.github.trojan_gfw.igniter.R;
16 |
17 | public class LoadingDialog extends Dialog {
18 | private TextView mMsgTv;
19 |
20 | public LoadingDialog(@NonNull Context context) {
21 | super(context);
22 | init(context);
23 | }
24 |
25 | private void init(Context context) {
26 | Window window =getWindow();
27 | if (window != null) {
28 | window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
29 | }
30 | setContentView(R.layout.dialog_loading);
31 | ContentLoadingProgressBar pb = findViewById(R.id.dialogLoadingPb);
32 | pb.getIndeterminateDrawable().setColorFilter(ContextCompat.getColor(context, R.color.colorPrimary),
33 | PorterDuff.Mode.MULTIPLY);
34 | mMsgTv = findViewById(R.id.dialogLoadingMsgTv);
35 | }
36 |
37 | public void setMsg(String msg) {
38 | mMsgTv.setText(msg);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/mvp/BasePresenter.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.mvp;
2 |
3 | public interface BasePresenter {
4 | void start();
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/mvp/BaseView.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.mvp;
2 |
3 | public interface BaseView {
4 | void setPresenter(T presenter);
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/os/IThreads.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.os;
2 |
3 | import java.util.concurrent.Executor;
4 |
5 | /**
6 | * Interface of thread pool. Provides methods to run {@link Task} or {@link Runnable} in main thread
7 | * or in background.
8 | */
9 | public interface IThreads {
10 | Executor getThreadPoolExecutor();
11 |
12 | void runOnUiThread(Runnable runnable);
13 |
14 | void runOnUiThread(Runnable runnable, long delayMillis);
15 |
16 | void runOnWorkThread(Task task);
17 |
18 | void runOnWorkThread(final Task task, long delayMillis);
19 |
20 | void removeDelayedAction(Runnable action);
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/os/PreferencesProvider.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.os;
2 |
3 | import android.content.ContentProvider;
4 | import android.content.ContentValues;
5 | import android.content.Context;
6 | import android.content.UriMatcher;
7 | import android.content.pm.ProviderInfo;
8 | import android.database.Cursor;
9 | import android.database.MatrixCursor;
10 | import android.net.Uri;
11 |
12 | import org.json.JSONException;
13 | import org.json.JSONObject;
14 |
15 | import java.io.BufferedReader;
16 | import java.io.File;
17 | import java.io.FileInputStream;
18 | import java.io.FileOutputStream;
19 | import java.io.IOException;
20 | import java.io.InputStreamReader;
21 | import java.io.OutputStreamWriter;
22 | import java.util.Iterator;
23 | import java.util.LinkedHashMap;
24 | import java.util.Map;
25 | import java.util.Objects;
26 | import java.util.Set;
27 |
28 | import io.github.trojan_gfw.igniter.Globals;
29 | import io.github.trojan_gfw.igniter.LogHelper;
30 | import io.github.trojan_gfw.igniter.common.constants.Constants;
31 |
32 | public class PreferencesProvider extends ContentProvider {
33 | private static final String TAG = "PreferencesProvider";
34 | public static final String AUTHORITY = Constants.PREFERENCE_AUTHORITY;
35 | public static final String PATH = Constants.PREFERENCE_PATH;
36 | private static final int CODE_PREFERENCES = 2077;
37 |
38 | private Map mCachePreferences;
39 | private final UriMatcher mUriMatcher;
40 | private String mPreferencesFilePath;
41 |
42 | public PreferencesProvider() {
43 | mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
44 | mUriMatcher.addURI(AUTHORITY, PATH, CODE_PREFERENCES);
45 | }
46 |
47 | @Override
48 | public void attachInfo(Context context, ProviderInfo info) {
49 | super.attachInfo(context, info);
50 | LogHelper.e(TAG, "attachInfo");
51 | mPreferencesFilePath = Globals.getPreferencesFilePath();
52 | }
53 |
54 | private boolean isUriNotValid(Uri uri) {
55 | return mUriMatcher.match(uri) != CODE_PREFERENCES;
56 | }
57 |
58 | @Override
59 | public int delete(Uri uri, String selection, String[] selectionArgs) {
60 | if (isUriNotValid(uri)) return 0;
61 | mCachePreferences.clear();
62 | File preferencesFile = new File(mPreferencesFilePath);
63 | if (preferencesFile.exists()) {
64 | preferencesFile.delete();
65 | return 1;
66 | }
67 | return 0;
68 | }
69 |
70 | @Override
71 | public String getType(Uri uri) {
72 | return "text/plain";
73 | }
74 |
75 | @Override
76 | public Uri insert(Uri uri, ContentValues values) {
77 | if (isUriNotValid(uri)) return null;
78 | update(uri, values, null, null);
79 | return uri;
80 | }
81 |
82 | @Override
83 | public boolean onCreate() {
84 | return true;
85 | }
86 |
87 | private void ensureCacheReady() {
88 | if (mCachePreferences == null) {
89 | mCachePreferences = new LinkedHashMap<>();
90 | readPreferencesToCache();
91 | }
92 | }
93 |
94 | private void readPreferencesToCache() {
95 | File preferencesFile = new File(mPreferencesFilePath);
96 | if (!preferencesFile.exists()) return;
97 | try (FileInputStream fis = new FileInputStream(preferencesFile);
98 | InputStreamReader isr = new InputStreamReader(fis);
99 | BufferedReader reader = new BufferedReader(isr)) {
100 | StringBuilder sb = new StringBuilder();
101 | String tmp;
102 | while ((tmp = reader.readLine()) != null) {
103 | sb.append(tmp);
104 | }
105 | JSONObject jsonObject = new JSONObject(sb.toString());
106 | Iterator it = jsonObject.keys();
107 | while (it.hasNext()) {
108 | String key = it.next();
109 | mCachePreferences.put(key, jsonObject.opt(key));
110 | }
111 | } catch (IOException | JSONException e) {
112 | e.printStackTrace();
113 | }
114 | }
115 |
116 | @Override
117 | public Cursor query(Uri uri, String[] projection, String selection,
118 | String[] selectionArgs, String sortOrder) {
119 | LogHelper.i(TAG, "query values with: " + this);
120 | if (isUriNotValid(uri)) {
121 | return new MatrixCursor(new String[0], 1);
122 | }
123 | ensureCacheReady();
124 | String[] queryKeys;
125 | if (projection == null) {
126 | Set keySet = mCachePreferences.keySet();
127 | queryKeys = new String[keySet.size()];
128 | int i = 0;
129 | for (String key : keySet) {
130 | queryKeys[i++] = key;
131 | }
132 | } else {
133 | queryKeys = projection;
134 | }
135 | MatrixCursor cursor = new MatrixCursor(queryKeys, 1);
136 | Object[] values = new Object[queryKeys.length];
137 | for (int i = 0; i < queryKeys.length; i++) {
138 | values[i] = mCachePreferences.get(queryKeys[i]);
139 | }
140 | cursor.addRow(values);
141 | return cursor;
142 | }
143 |
144 | @Override
145 | public int update(Uri uri, ContentValues values, String selection,
146 | String[] selectionArgs) {
147 | LogHelper.i(TAG, "update values with: " + this);
148 | if (isUriNotValid(uri)) {
149 | return 0;
150 | }
151 | ensureCacheReady();
152 | Set keySet = values.keySet();
153 | boolean valueChanged = false;
154 | for (String key : keySet) {
155 | Object previousValue = mCachePreferences.get(key);
156 | Object nextValue = values.get(key);
157 | if (!Objects.equals(previousValue, nextValue)) {
158 | valueChanged = true;
159 | mCachePreferences.put(key, nextValue);
160 | }
161 | }
162 | if (valueChanged) {
163 | writeCacheIntoFile();
164 | return 1;
165 | }
166 | return 0;
167 | }
168 |
169 | private void writeCacheIntoFile() {
170 | if (mCachePreferences == null) return;
171 | LogHelper.i(TAG, "write preferences to file: " + this);
172 | JSONObject jsonObject = new JSONObject();
173 | try {
174 | for (String key : mCachePreferences.keySet()) {
175 | jsonObject.put(key, mCachePreferences.get(key));
176 | }
177 | } catch (JSONException e) {
178 | e.printStackTrace();
179 | }
180 | File preferencesFile = new File(mPreferencesFilePath);
181 | try (FileOutputStream fos = new FileOutputStream(preferencesFile);
182 | OutputStreamWriter osw = new OutputStreamWriter(fos)) {
183 | osw.write(jsonObject.toString());
184 | osw.flush();
185 | } catch (IOException e) {
186 | e.printStackTrace();
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/os/Task.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.os;
2 |
3 |
4 | import android.os.Process;
5 |
6 | import androidx.annotation.WorkerThread;
7 |
8 | /**
9 | * A wrapper of Runnable.
10 | */
11 | public abstract class Task implements Runnable {
12 | private int priority = Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE;
13 |
14 | public Task() {
15 | }
16 |
17 | /**
18 | * Construct a task with priority.
19 | *
20 | * @param priority {@link Process#THREAD_PRIORITY_BACKGROUND}
21 | */
22 | public Task(int priority) {
23 | this.priority = priority;
24 | }
25 |
26 | @Override
27 | public void run() {
28 | Process.setThreadPriority(priority);
29 | onRun();
30 | }
31 |
32 | @WorkerThread
33 | public abstract void onRun();
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/os/Threads.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.os;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 |
6 | import java.util.concurrent.Executor;
7 | import java.util.concurrent.ExecutorService;
8 | import java.util.concurrent.SynchronousQueue;
9 | import java.util.concurrent.ThreadFactory;
10 | import java.util.concurrent.ThreadPoolExecutor;
11 | import java.util.concurrent.TimeUnit;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | /**
15 | * Singleton implementation of {@link IThreads}. Call {@link #instance()} to get the instance.
16 | */
17 | public final class Threads implements IThreads {
18 | private ExecutorService mThreadPool;
19 | private Handler mHandler;
20 |
21 | private Threads() {
22 | mThreadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
23 | 30L, TimeUnit.SECONDS,
24 | new SynchronousQueue(),
25 | new DefaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
26 | mHandler = new Handler(Looper.getMainLooper());
27 | }
28 |
29 | /**
30 | * The default thread factory
31 | */
32 | private static class DefaultThreadFactory implements ThreadFactory {
33 | private static final AtomicInteger poolNumber = new AtomicInteger(1);
34 | private final ThreadGroup group;
35 | private final AtomicInteger threadNumber = new AtomicInteger(1);
36 | private final String namePrefix;
37 |
38 | DefaultThreadFactory() {
39 | SecurityManager s = System.getSecurityManager();
40 | group = (s != null) ? s.getThreadGroup() :
41 | Thread.currentThread().getThreadGroup();
42 | namePrefix = "ThreadHelperPool-" +
43 | poolNumber.getAndIncrement() +
44 | "-thread-";
45 | }
46 |
47 | @Override
48 | public Thread newThread(Runnable r) {
49 | Thread t = new Thread(group, r,
50 | namePrefix + threadNumber.getAndIncrement(),
51 | 0);
52 | if (t.isDaemon())
53 | t.setDaemon(false);
54 | return t;
55 | }
56 | }
57 |
58 | public static IThreads instance() {
59 | return Holder.INSTANCE;
60 | }
61 |
62 | private static class Holder {
63 | private static final Threads INSTANCE = new Threads();
64 | }
65 |
66 | @Override
67 | public Executor getThreadPoolExecutor() {
68 | return mThreadPool;
69 | }
70 |
71 | @Override
72 | public void runOnWorkThread(Task task) {
73 | mThreadPool.execute(task);
74 | }
75 |
76 | @Override
77 | public void runOnWorkThread(final Task task, long delayMillis) {
78 | mHandler.postDelayed(new Runnable() {
79 | @Override
80 | public void run() {
81 | mThreadPool.execute(task);
82 | }
83 | }, delayMillis);
84 | }
85 |
86 | @Override
87 | public void runOnUiThread(Runnable action) {
88 | mHandler.post(action);
89 | }
90 |
91 | @Override
92 | public void runOnUiThread(Runnable action, long delayMillis) {
93 | mHandler.postDelayed(action, delayMillis);
94 | }
95 |
96 | @Override
97 | public void removeDelayedAction(Runnable action) {
98 | mHandler.removeCallbacks(action);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/PermissionUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 |
7 | import androidx.core.content.ContextCompat;
8 |
9 | public abstract class PermissionUtils {
10 |
11 | public static boolean hasReadWriteExtStoragePermission(Context context) {
12 | return ContextCompat.checkSelfPermission(context,
13 | Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
14 | && ContextCompat.checkSelfPermission(context,
15 | Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/PreferenceUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.ContentValues;
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 |
8 | import androidx.core.content.ContentResolverCompat;
9 |
10 | public abstract class PreferenceUtils {
11 |
12 | public static boolean getBooleanPreference(ContentResolver resolver, Uri uri, String key, boolean defaultValue) {
13 | try (Cursor query = ContentResolverCompat.query(resolver, uri, new String[]{key}, null,
14 | null, null, null)) {
15 | if (query.moveToFirst()) {
16 | int columnIndex = query.getColumnIndex(key);
17 | if (columnIndex >= 0) {
18 | int type = query.getType(columnIndex);
19 | switch (type) {
20 | case Cursor.FIELD_TYPE_STRING:
21 | return Boolean.parseBoolean(query.getString(columnIndex));
22 | case Cursor.FIELD_TYPE_INTEGER:
23 | return query.getInt(columnIndex) == 1;
24 | default:
25 | return defaultValue;
26 | }
27 | }
28 | }
29 | }
30 | return defaultValue;
31 | }
32 |
33 | public static void putBooleanPreference(ContentResolver resolver, Uri uri, String key, boolean value) {
34 | ContentValues contentValues = new ContentValues(1);
35 | contentValues.put(key, value);
36 | resolver.update(uri, contentValues, null, null);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/ProcessUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | import java.util.List;
9 |
10 | import static android.content.Context.ACTIVITY_SERVICE;
11 |
12 | public class ProcessUtils {
13 |
14 | @Nullable
15 | public static String getProcessNameByPID(Context context, int pid) {
16 | ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
17 | List runningAppProcesses = am.getRunningAppProcesses();
18 | if (runningAppProcesses == null) {
19 | return null;
20 | }
21 | for (ActivityManager.RunningAppProcessInfo info : runningAppProcesses) {
22 | if (info.pid == pid) {
23 | return info.processName;
24 | }
25 | }
26 | return null;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/SnackbarUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.view.View;
4 |
5 | import androidx.annotation.StringRes;
6 |
7 | import com.google.android.material.snackbar.Snackbar;
8 |
9 | public class SnackbarUtils {
10 |
11 | public static void showTextShort(View view, @StringRes int id) {
12 | Snackbar.make(view, id, Snackbar.LENGTH_SHORT).show();
13 | }
14 |
15 | public static void showTextShort(View view, @StringRes int id, @StringRes int actionId, View.OnClickListener listener) {
16 | Snackbar.make(view, id, Snackbar.LENGTH_SHORT).setAction(actionId, listener).show();
17 | }
18 |
19 | public static void showTextShort(View view, String text, String actionText, View.OnClickListener listener) {
20 | Snackbar.make(view, text, Snackbar.LENGTH_SHORT).setAction(actionText, listener).show();
21 | }
22 |
23 | public static void showTextShort(View view, String text) {
24 | Snackbar.make(view, text, Snackbar.LENGTH_SHORT).show();
25 | }
26 |
27 | public static void showTextLong(View view, @StringRes int id) {
28 | Snackbar.make(view, id, Snackbar.LENGTH_LONG).show();
29 | }
30 |
31 | public static void showTextLong(View view, @StringRes int id, @StringRes int actionId, View.OnClickListener listener) {
32 | Snackbar.make(view, id, Snackbar.LENGTH_LONG).setAction(actionId, listener).show();
33 | }
34 |
35 | public static void showTextLong(View view, String text) {
36 | Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
37 | }
38 |
39 | public static void showTextLong(View view, String text, String actionText, View.OnClickListener listener) {
40 | Snackbar.make(view, text, Snackbar.LENGTH_LONG).setAction(actionText, listener).show();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/connection/TestConnection.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.connection;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import java.lang.ref.WeakReference;
8 | import java.net.InetSocketAddress;
9 | import java.net.Proxy;
10 | import java.net.URL;
11 | import java.net.URLConnection;
12 |
13 | public class TestConnection extends AsyncTask {
14 | private static final int DEFAULT_TIMEOUT = 10 * 1000; // 10 seconds
15 | private final String mProxyHost;
16 | private final long mProxyPort;
17 | private final WeakReference mOnResultListenerRef;
18 |
19 | public TestConnection(String proxyHost, long proxyPort, OnResultListener onResultListener) {
20 | mProxyHost = proxyHost;
21 | mProxyPort = proxyPort;
22 | mOnResultListenerRef = new WeakReference<>(onResultListener);
23 | }
24 |
25 | @Override
26 | protected TestResult doInBackground(String... strings) {
27 | String testUrl = strings[0];
28 | try {
29 | long startTime = System.currentTimeMillis();
30 | InetSocketAddress proxyAddress = new InetSocketAddress(mProxyHost, (int) mProxyPort);
31 | Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress);
32 | URLConnection connection = new URL(testUrl).openConnection(proxy);
33 | connection.setConnectTimeout(DEFAULT_TIMEOUT);
34 | connection.setReadTimeout(DEFAULT_TIMEOUT);
35 | connection.connect();
36 | return new TestResult(testUrl, true, "",
37 | System.currentTimeMillis() - startTime);
38 | } catch (Exception e) {
39 | return new TestResult(testUrl, false, e.getMessage(), 0);
40 | }
41 | }
42 |
43 | @Override
44 | protected void onPostExecute(TestResult testResult) {
45 | OnResultListener listener = mOnResultListenerRef.get();
46 | if (listener != null) {
47 | listener.onResult(testResult.url, testResult.connected, testResult.delay, testResult.error);
48 | }
49 | }
50 |
51 | public interface OnResultListener {
52 | void onResult(String testUrl, boolean connected, long delay, String error);
53 | }
54 | }
55 |
56 | class TestResult {
57 | boolean connected;
58 | String url;
59 | String error;
60 | long delay;
61 |
62 | TestResult(String url, boolean connected, @NonNull String error, long delay) {
63 | this.connected = connected;
64 | this.url = url;
65 | this.error = error;
66 | this.delay = delay;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/connection/TrojanConnection.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.connection;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.ServiceConnection;
7 | import android.os.Binder;
8 | import android.os.Handler;
9 | import android.os.IBinder;
10 | import android.os.RemoteException;
11 |
12 | import androidx.annotation.NonNull;
13 |
14 | import io.github.trojan_gfw.igniter.ProxyService;
15 | import io.github.trojan_gfw.igniter.R;
16 | import io.github.trojan_gfw.igniter.proxy.aidl.ITrojanService;
17 | import io.github.trojan_gfw.igniter.proxy.aidl.ITrojanServiceCallback;
18 |
19 | /**
20 | * A class that delegates interaction with {@link ProxyService}. You should call {@link #connect(Context, Callback)}
21 | * when you are ready for interacting with {@link ProxyService} and call {@link #disconnect(Context)}
22 | * in the end. {@link TrojanConnection} would bind {@link ProxyService} and register {@link ITrojanServiceCallback}.
23 | * You can easily obtain {@link ProxyService} and get state change as well as connection test result
24 | * by implementing {@link Callback}.
25 | *
26 | * @see ProxyService
27 | * @see ITrojanService
28 | * @see ITrojanServiceCallback
29 | */
30 | public class TrojanConnection implements ServiceConnection, Binder.DeathRecipient {
31 | private final Handler mHandler = new Handler();
32 | private ITrojanService mTrojanService;
33 | private Callback mCallback;
34 | private boolean mServiceCallbackRegistered;
35 | private final boolean mListenToDeath;
36 | private boolean mAlreadyConnected;
37 | private IBinder mBinder;
38 | /**
39 | * Implementation of {@link ITrojanServiceCallback}. The callback is registered in {@link #onServiceConnected(ComponentName, IBinder)},
40 | * and unregistered in {@link #onServiceDisconnected(ComponentName)}. The callback is considered
41 | * to be invoked by {@link ITrojanService}, in this case, a field of {@link ProxyService} implements
42 | * {@link ITrojanService}.
43 | *
44 | * @see ProxyService
45 | * @see ITrojanService
46 | */
47 | private ITrojanServiceCallback mTrojanServiceCallback = new ITrojanServiceCallback.Stub() {
48 | @Override
49 | public void onStateChanged(final int state, final String msg) throws RemoteException {
50 | if (mCallback != null) {
51 | mHandler.post(new Runnable() {
52 | @Override
53 | public void run() {
54 | mCallback.onStateChanged(state, msg);
55 | }
56 | });
57 | }
58 | }
59 |
60 | @Override
61 | public void onTestResult(final String testUrl, final boolean connected, final long delay, final String error) throws RemoteException {
62 | if (mCallback != null) {
63 | mHandler.post(new Runnable() {
64 | @Override
65 | public void run() {
66 | mCallback.onTestResult(testUrl, connected, delay, error);
67 | }
68 | });
69 | }
70 | }
71 | };
72 |
73 | public TrojanConnection(boolean listenToDeath) {
74 | super();
75 | mListenToDeath = listenToDeath;
76 | }
77 |
78 | /**
79 | * Callback for events that are relative to {@link ProxyService}.
80 | */
81 | public interface Callback {
82 | void onServiceConnected(ITrojanService service);
83 |
84 | void onServiceDisconnected();
85 |
86 | void onStateChanged(int state, String msg);
87 |
88 | void onTestResult(String testUrl, boolean connected, long delay, @NonNull String error);
89 |
90 | void onBinderDied();
91 | }
92 |
93 | public void connect(Context context, Callback callback) {
94 | if (mAlreadyConnected) {
95 | return;
96 | }
97 | mAlreadyConnected = true;
98 | if (mCallback != null) {
99 | throw new IllegalStateException("Required to call disconnect(Context) first.");
100 | }
101 | mCallback = callback;
102 |
103 | // todo: choose the service class dynamically.
104 | Intent intent = new Intent(context, ProxyService.class);
105 | intent.setAction(context.getString(R.string.bind_service));
106 | context.bindService(intent, this, Context.BIND_AUTO_CREATE);
107 | }
108 |
109 | public void disconnect(Context context) {
110 | unregisterServiceCallback();
111 | if (mAlreadyConnected) {
112 | try {
113 | context.unbindService(this);
114 | } catch (IllegalArgumentException e) {
115 | e.printStackTrace();
116 | }
117 | mAlreadyConnected = false;
118 | if (mListenToDeath && mBinder != null) {
119 | mBinder.unlinkToDeath(this, 0);
120 | }
121 | mBinder = null;
122 | mTrojanService = null;
123 | mCallback = null;
124 | }
125 | }
126 |
127 | private void unregisterServiceCallback() {
128 | ITrojanService service = mTrojanService;
129 | if (service != null && mServiceCallbackRegistered) {
130 | try {
131 | service.unregisterCallback(mTrojanServiceCallback);
132 | } catch (RemoteException e) {
133 | e.printStackTrace();
134 | }
135 | mServiceCallbackRegistered = false;
136 | }
137 | }
138 |
139 | public ITrojanService getService() {
140 | return mTrojanService;
141 | }
142 |
143 | /**
144 | * Obtain the binder {@link ITrojanService} returned by {@link ProxyService#onBind(Intent)} and
145 | * register callback {@link #mTrojanServiceCallback} with the binder.
146 | */
147 | @Override
148 | public void onServiceConnected(ComponentName name, IBinder binder) {
149 | mBinder = binder;
150 | ITrojanService service = ITrojanService.Stub.asInterface(binder);
151 | mTrojanService = service;
152 | try {
153 | if (mListenToDeath) {
154 | binder.linkToDeath(this, 0);
155 | }
156 | if (mServiceCallbackRegistered) {
157 | throw new IllegalStateException("TrojanServiceCallback already registered!");
158 | }
159 | service.registerCallback(mTrojanServiceCallback);
160 | mServiceCallbackRegistered = true;
161 | } catch (RemoteException e) {
162 | e.printStackTrace();
163 | }
164 | if (mCallback != null) {
165 | mCallback.onServiceConnected(service);
166 | }
167 | }
168 |
169 | @Override
170 | public void onServiceDisconnected(ComponentName name) {
171 | unregisterServiceCallback();
172 | if (mCallback != null) {
173 | mCallback.onServiceDisconnected();
174 | }
175 | mTrojanService = null;
176 | mBinder = null;
177 | }
178 |
179 | @Override
180 | public void binderDied() {
181 | mTrojanService = null;
182 | mServiceCallbackRegistered = false;
183 | if (mCallback != null) {
184 | mHandler.post(new Runnable() {
185 | @Override
186 | public void run() {
187 | mCallback.onBinderDied();
188 | }
189 | });
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/activity/ExemptAppActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.activity;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.Window;
7 |
8 | import androidx.fragment.app.FragmentManager;
9 |
10 | import io.github.trojan_gfw.igniter.Globals;
11 | import io.github.trojan_gfw.igniter.R;
12 | import io.github.trojan_gfw.igniter.common.app.BaseAppCompatActivity;
13 | import io.github.trojan_gfw.igniter.exempt.contract.ExemptAppContract;
14 | import io.github.trojan_gfw.igniter.exempt.data.ExemptAppDataManager;
15 | import io.github.trojan_gfw.igniter.exempt.fragment.ExemptAppFragment;
16 | import io.github.trojan_gfw.igniter.exempt.presenter.ExemptAppPresenter;
17 |
18 | public class ExemptAppActivity extends BaseAppCompatActivity {
19 | private ExemptAppContract.Presenter mPresenter;
20 |
21 | public static Intent create(Context context) {
22 | return new Intent(context, ExemptAppActivity.class);
23 | }
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | requestWindowFeature(Window.FEATURE_NO_TITLE);
29 | setContentView(R.layout.activity_exempt_app);
30 | FragmentManager fm = getSupportFragmentManager();
31 | ExemptAppFragment fragment = (ExemptAppFragment) fm.findFragmentByTag(ExemptAppFragment.TAG);
32 | if (fragment == null) {
33 | fragment = ExemptAppFragment.newInstance();
34 | }
35 | mPresenter = new ExemptAppPresenter(fragment, new ExemptAppDataManager(getApplicationContext(),
36 | Globals.getExemptedAppListPath()));
37 | fm.beginTransaction()
38 | .replace(R.id.parent_fl, fragment, ExemptAppFragment.TAG)
39 | .commitAllowingStateLoss();
40 | }
41 |
42 | @Override
43 | public void onBackPressed() {
44 | if (mPresenter == null || !mPresenter.handleBackPressed()) {
45 | super.onBackPressed();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/adapter/AppInfoAdapter.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.adapter;
2 |
3 | import android.content.res.Resources;
4 | import android.graphics.Rect;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.CompoundButton;
9 | import android.widget.Switch;
10 | import android.widget.TextView;
11 |
12 | import androidx.annotation.NonNull;
13 | import androidx.core.widget.TextViewCompat;
14 | import androidx.recyclerview.widget.RecyclerView;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import io.github.trojan_gfw.igniter.R;
20 | import io.github.trojan_gfw.igniter.exempt.data.AppInfo;
21 |
22 | public class AppInfoAdapter extends RecyclerView.Adapter {
23 | private final List mData = new ArrayList<>();
24 | private OnItemOperationListener mOnItemOperationListener;
25 | private final Rect mIconBound = new Rect();
26 |
27 | public AppInfoAdapter() {
28 | super();
29 | final int size = Resources.getSystem().getDimensionPixelSize(android.R.dimen.app_icon_size);
30 | mIconBound.right = size;
31 | mIconBound.bottom = size;
32 | }
33 |
34 | @NonNull
35 | @Override
36 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
37 | View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_app_info, viewGroup, false);
38 | return new ViewHolder(v);
39 | }
40 |
41 | @Override
42 | public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
43 | if (i != RecyclerView.NO_POSITION) {
44 | viewHolder.bindData(mData.get(i));
45 | }
46 | }
47 |
48 | public void removeData(int position) {
49 | mData.remove(position);
50 | notifyItemRemoved(position);
51 | }
52 |
53 | public void refreshData(List data) {
54 | mData.clear();
55 | mData.addAll(data);
56 | notifyDataSetChanged();
57 | }
58 |
59 | @Override
60 | public int getItemCount() {
61 | return mData.size();
62 | }
63 |
64 | public void setOnItemOperationListener(OnItemOperationListener onItemOperationListener) {
65 | mOnItemOperationListener = onItemOperationListener;
66 | }
67 |
68 | public interface OnItemOperationListener {
69 | void onToggle(boolean exempt, AppInfo appInfo, int position);
70 | }
71 |
72 | class ViewHolder extends RecyclerView.ViewHolder implements CompoundButton.OnCheckedChangeListener {
73 | private TextView mNameTv;
74 | private Switch mExemptSwitch;
75 | private AppInfo mCurrentInfo;
76 |
77 | ViewHolder(@NonNull View itemView) {
78 | super(itemView);
79 | mNameTv = itemView.findViewById(R.id.appNameTv);
80 | TextViewCompat.setAutoSizeTextTypeWithDefaults(mNameTv, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
81 | mExemptSwitch = itemView.findViewById(R.id.appExemptSwitch);
82 | }
83 |
84 | @Override
85 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
86 | if (mOnItemOperationListener != null) {
87 | mOnItemOperationListener.onToggle(isChecked, mCurrentInfo, getBindingAdapterPosition());
88 | }
89 | }
90 |
91 | void bindData(AppInfo appInfo) {
92 | mCurrentInfo = appInfo;
93 | mNameTv.setText(appInfo.getAppName());
94 | appInfo.getIcon().setBounds(mIconBound);
95 | mNameTv.setCompoundDrawables(appInfo.getIcon(), null, null, null);
96 | mExemptSwitch.setOnCheckedChangeListener(null);
97 | mExemptSwitch.setChecked(appInfo.isExempt());
98 | mExemptSwitch.setOnCheckedChangeListener(this);
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/contract/ExemptAppContract.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.contract;
2 |
3 | import androidx.annotation.AnyThread;
4 | import androidx.annotation.UiThread;
5 |
6 | import java.util.List;
7 |
8 | import io.github.trojan_gfw.igniter.common.mvp.BasePresenter;
9 | import io.github.trojan_gfw.igniter.common.mvp.BaseView;
10 | import io.github.trojan_gfw.igniter.exempt.data.AppInfo;
11 |
12 | public interface ExemptAppContract {
13 | interface Presenter extends BasePresenter {
14 | void updateAppInfo(AppInfo appInfo, int position, boolean exempt);
15 |
16 | void saveExemptAppInfoList();
17 |
18 | /**
19 | * @return true if exit directly, false to cancel exiting.
20 | */
21 | boolean handleBackPressed();
22 |
23 | void filterAppsByName(String name);
24 |
25 | void exit();
26 | }
27 |
28 | interface View extends BaseView {
29 | @UiThread
30 | void showLoading();
31 |
32 | @UiThread
33 | void dismissLoading();
34 |
35 | @UiThread
36 | void showSaveSuccess();
37 |
38 | @UiThread
39 | void showExitConfirm();
40 |
41 | @UiThread
42 | void showAppList(List packageNames);
43 |
44 | @AnyThread
45 | void exit(boolean configurationChanged);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/data/AppInfo.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.data;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | public class AppInfo implements Cloneable {
6 | private String appName;
7 | private String appNameInLowercase;
8 | private Drawable icon;
9 | private String packageName;
10 | private boolean exempt;
11 |
12 | public String getAppName() {
13 | return appName;
14 | }
15 |
16 | public String getAppNameInLowercase() {
17 | if (appNameInLowercase == null) {
18 | // lazy loading.
19 | appNameInLowercase = appName.toLowerCase();
20 | }
21 | return appNameInLowercase;
22 | }
23 |
24 | public void setAppName(String appName) {
25 | this.appName = appName;
26 | }
27 |
28 | public Drawable getIcon() {
29 | return icon;
30 | }
31 |
32 | public void setIcon(Drawable icon) {
33 | this.icon = icon;
34 | }
35 |
36 | public String getPackageName() {
37 | return packageName;
38 | }
39 |
40 | public void setPackageName(String packageName) {
41 | this.packageName = packageName;
42 | }
43 |
44 | public boolean isExempt() {
45 | return exempt;
46 | }
47 |
48 | public void setExempt(boolean exempt) {
49 | this.exempt = exempt;
50 | }
51 |
52 | @Override
53 | protected Object clone() throws CloneNotSupportedException {
54 | AppInfo appInfo = (AppInfo) super.clone();
55 | appInfo.appName = appName;
56 | appInfo.icon = icon;
57 | appInfo.packageName = packageName;
58 | appInfo.exempt = exempt;
59 | return appInfo;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/data/ExemptAppDataManager.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.data;
2 |
3 | import android.content.Context;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | import android.text.TextUtils;
8 |
9 | import androidx.annotation.NonNull;
10 |
11 | import java.io.BufferedReader;
12 | import java.io.BufferedWriter;
13 | import java.io.File;
14 | import java.io.FileInputStream;
15 | import java.io.FileOutputStream;
16 | import java.io.IOException;
17 | import java.io.InputStreamReader;
18 | import java.io.OutputStreamWriter;
19 | import java.util.ArrayList;
20 | import java.util.HashSet;
21 | import java.util.List;
22 | import java.util.Set;
23 |
24 | /**
25 | * Implementation of {@link ExemptAppDataSource}. This class reads and writes exempted app list in a
26 | * file. The exempted app package names will be written line by line in the file.
27 | *
28 | * Example:
29 | *
30 | * com.google.playstore
31 | *
32 | * io.github.trojan_gfw.igniter
33 | *
34 | * com.android.something
35 | */
36 | public class ExemptAppDataManager implements ExemptAppDataSource {
37 | private final PackageManager mPackageManager;
38 | private final String mExemptAppListFilePath;
39 |
40 | public ExemptAppDataManager(Context context, String exemptAppListFilePath) {
41 | super();
42 | mPackageManager = context.getPackageManager();
43 | mExemptAppListFilePath = exemptAppListFilePath;
44 | }
45 |
46 | @Override
47 | public void saveExemptAppInfoSet(Set exemptAppPackageNames) {
48 | File file = new File(mExemptAppListFilePath);
49 | if (file.exists()) {
50 | file.delete();
51 | }
52 | if (exemptAppPackageNames == null || exemptAppPackageNames.isEmpty()) {
53 | return;
54 | }
55 | File dir = file.getParentFile();
56 | if (!dir.exists()) {
57 | dir.mkdirs();
58 | }
59 | try (FileOutputStream fos = new FileOutputStream(file);
60 | OutputStreamWriter osw = new OutputStreamWriter(fos);
61 | BufferedWriter bw = new BufferedWriter(osw)) {
62 | for (String name : exemptAppPackageNames) {
63 | bw.write(name);
64 | bw.write('\n');
65 | }
66 | bw.flush();
67 | } catch (IOException e) {
68 | e.printStackTrace();
69 | }
70 | }
71 |
72 | @NonNull
73 | private Set readExemptAppListConfig() {
74 | File file = new File(mExemptAppListFilePath);
75 | Set exemptAppSet = new HashSet<>();
76 | if (!file.exists()) {
77 | return exemptAppSet;
78 | }
79 | try (FileInputStream fis = new FileInputStream(file);
80 | InputStreamReader isr = new InputStreamReader(fis);
81 | BufferedReader reader = new BufferedReader(isr)) {
82 | String tmp = reader.readLine();
83 | while (!TextUtils.isEmpty(tmp)) {
84 | exemptAppSet.add(tmp);
85 | tmp = reader.readLine();
86 | }
87 | } catch (IOException e) {
88 | e.printStackTrace();
89 | }
90 | return exemptAppSet;
91 | }
92 |
93 | @Override
94 | public Set loadExemptAppPackageNameSet() {
95 | Set exemptAppPackageNames = readExemptAppListConfig();
96 | // filter uninstalled apps
97 | List applicationInfoList = queryCurrentInstalledApps();
98 | Set installedAppPackageNames = new HashSet<>();
99 | for (ApplicationInfo applicationInfo : applicationInfoList) {
100 | installedAppPackageNames.add(applicationInfo.packageName);
101 | }
102 | Set ret = new HashSet<>();
103 | for (String packageName : exemptAppPackageNames) {
104 | if (installedAppPackageNames.contains(packageName)) {
105 | ret.add(packageName);
106 | }
107 | }
108 | return ret;
109 | }
110 |
111 | private List queryCurrentInstalledApps() {
112 | int flags = 0;
113 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
114 | flags |= PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS;
115 | } else { // These flags are deprecated since Nougat.
116 | flags |= PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS;
117 | }
118 | return mPackageManager.getInstalledApplications(flags);
119 | }
120 |
121 | @Override
122 | public List getAllAppInfoList() {
123 | List applicationInfoList = queryCurrentInstalledApps();
124 | List appInfoList = new ArrayList<>(applicationInfoList.size());
125 | for (ApplicationInfo applicationInfo : applicationInfoList) {
126 | AppInfo appInfo = new AppInfo();
127 | appInfo.setAppName(mPackageManager.getApplicationLabel(applicationInfo).toString());
128 | appInfo.setPackageName(applicationInfo.packageName);
129 | appInfo.setIcon(mPackageManager.getApplicationIcon(applicationInfo));
130 | appInfoList.add(appInfo);
131 | }
132 | return appInfoList;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/data/ExemptAppDataSource.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.data;
2 |
3 | import androidx.annotation.WorkerThread;
4 |
5 | import java.util.List;
6 | import java.util.Set;
7 |
8 | public interface ExemptAppDataSource {
9 | /**
10 | * Load exempt applications' package names.
11 | *
12 | * @return exempt applications' package names..
13 | */
14 | @WorkerThread
15 | Set loadExemptAppPackageNameSet();
16 |
17 | /**
18 | * Save exempt applications' package names.
19 | *
20 | * @param exemptAppPackageNames exempt app package name set
21 | */
22 | @WorkerThread
23 | void saveExemptAppInfoSet(Set exemptAppPackageNames);
24 |
25 | /**
26 | * Load all application info list, including exempt apps and non-exempt apps.
27 | * @return all application info list
28 | */
29 | @WorkerThread
30 | List getAllAppInfoList();
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/fragment/ExemptAppFragment.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.fragment;
2 |
3 | import android.app.Activity;
4 | import android.content.DialogInterface;
5 | import android.os.Bundle;
6 | import android.view.LayoutInflater;
7 | import android.view.Menu;
8 | import android.view.MenuInflater;
9 | import android.view.MenuItem;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 | import androidx.appcompat.app.AlertDialog;
16 | import androidx.appcompat.app.AppCompatActivity;
17 | import androidx.appcompat.widget.SearchView;
18 | import androidx.appcompat.widget.Toolbar;
19 | import androidx.fragment.app.FragmentActivity;
20 | import androidx.recyclerview.widget.DividerItemDecoration;
21 | import androidx.recyclerview.widget.LinearLayoutManager;
22 | import androidx.recyclerview.widget.RecyclerView;
23 |
24 | import java.util.List;
25 |
26 | import io.github.trojan_gfw.igniter.R;
27 | import io.github.trojan_gfw.igniter.common.app.BaseFragment;
28 | import io.github.trojan_gfw.igniter.common.dialog.LoadingDialog;
29 | import io.github.trojan_gfw.igniter.common.utils.SnackbarUtils;
30 | import io.github.trojan_gfw.igniter.exempt.adapter.AppInfoAdapter;
31 | import io.github.trojan_gfw.igniter.exempt.contract.ExemptAppContract;
32 | import io.github.trojan_gfw.igniter.exempt.data.AppInfo;
33 |
34 | public class ExemptAppFragment extends BaseFragment implements ExemptAppContract.View {
35 | public static final String TAG = "ExemptAppFragment";
36 | private ExemptAppContract.Presenter mPresenter;
37 | private Toolbar mTopBar;
38 | private RecyclerView mAppRv;
39 | private AppInfoAdapter mAppInfoAdapter;
40 | private LoadingDialog mLoadingDialog;
41 |
42 | public ExemptAppFragment() {
43 | // Required empty public constructor
44 | }
45 |
46 | public static ExemptAppFragment newInstance() {
47 | return new ExemptAppFragment();
48 | }
49 |
50 | @Override
51 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
52 | Bundle savedInstanceState) {
53 | // Inflate the layout for this fragment
54 | return inflater.inflate(R.layout.fragment_exempt_app, container, false);
55 | }
56 |
57 | @Override
58 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
59 | super.onViewCreated(view, savedInstanceState);
60 | findViews();
61 | initViews();
62 | initListeners();
63 | mPresenter.start();
64 | }
65 |
66 | private void findViews() {
67 | mTopBar = findViewById(R.id.exemptAppTopBar);
68 | mAppRv = findViewById(R.id.exemptAppRv);
69 | }
70 |
71 | private void initViews() {
72 | FragmentActivity activity = getActivity();
73 | if (activity instanceof AppCompatActivity) {
74 | ((AppCompatActivity) activity).setSupportActionBar(mTopBar);
75 | setHasOptionsMenu(true);
76 | }
77 | mAppInfoAdapter = new AppInfoAdapter();
78 | mAppRv.setAdapter(mAppInfoAdapter);
79 | mAppRv.addItemDecoration(new DividerItemDecoration(mContext, LinearLayoutManager.VERTICAL));
80 | }
81 |
82 | private void initListeners() {
83 | mAppInfoAdapter.setOnItemOperationListener(new AppInfoAdapter.OnItemOperationListener() {
84 | @Override
85 | public void onToggle(boolean exempt, AppInfo appInfo, int position) {
86 | mPresenter.updateAppInfo(appInfo, position, exempt);
87 | }
88 | });
89 | }
90 |
91 | @Override
92 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
93 | menu.clear();
94 | inflater.inflate(R.menu.menu_exempt_app, menu);
95 |
96 | MenuItem item = menu.findItem(R.id.action_search_app);
97 | SearchView searchView = null;
98 | if (item != null) {
99 | searchView = (SearchView) item.getActionView();
100 | }
101 | if (searchView != null) {
102 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
103 | @Override
104 | public boolean onQueryTextSubmit(String s) {
105 | return false;
106 | }
107 |
108 | @Override
109 | public boolean onQueryTextChange(String s) {
110 | mPresenter.filterAppsByName(s);
111 | return true;
112 | }
113 | });
114 | }
115 | }
116 |
117 | @Override
118 | public boolean onOptionsItemSelected(MenuItem item) {
119 | if (item.getItemId() == R.id.action_save_exempt_apps) {
120 | mPresenter.saveExemptAppInfoList();
121 | return true;
122 | }
123 | return false;
124 | }
125 |
126 | @Override
127 | public void showSaveSuccess() {
128 | SnackbarUtils.showTextShort(mRootView, R.string.common_save_success, R.string.exempt_app_exit, new View.OnClickListener() {
129 | @Override
130 | public void onClick(View v) {
131 | mPresenter.exit();
132 | }
133 | });
134 | }
135 |
136 | @Override
137 | public void showExitConfirm() {
138 | new AlertDialog.Builder(mContext)
139 | .setTitle(R.string.common_alert)
140 | .setMessage(R.string.exempt_app_exit_without_saving_confirm)
141 | .setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
142 | @Override
143 | public void onClick(DialogInterface dialog, int which) {
144 | dialog.dismiss();
145 | }
146 | })
147 | .setPositiveButton(R.string.common_confirm, new DialogInterface.OnClickListener() {
148 | @Override
149 | public void onClick(DialogInterface dialog, int which) {
150 | dialog.dismiss();
151 | mPresenter.exit();
152 | }
153 | }).create().show();
154 | }
155 |
156 | @Override
157 | public void showAppList(final List appInfoList) {
158 | mAppInfoAdapter.refreshData(appInfoList);
159 | }
160 |
161 | @Override
162 | public void showLoading() {
163 | if (mLoadingDialog == null) {
164 | mLoadingDialog = new LoadingDialog(requireContext());
165 | mLoadingDialog.setMsg(getString(R.string.exempt_app_loading_tip));
166 | }
167 | mLoadingDialog.show();
168 | }
169 |
170 | @Override
171 | public void dismissLoading() {
172 | if (mLoadingDialog != null && mLoadingDialog.isShowing()) {
173 | mLoadingDialog.dismiss();
174 | }
175 | }
176 |
177 | @Override
178 | public void exit(boolean configurationChanged) {
179 | Activity activity = getActivity();
180 | if (activity != null) {
181 | activity.setResult(configurationChanged ? Activity.RESULT_OK : Activity.RESULT_CANCELED);
182 | activity.finish();
183 | }
184 | }
185 |
186 | @Override
187 | public void setPresenter(ExemptAppContract.Presenter presenter) {
188 | mPresenter = presenter;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/exempt/presenter/ExemptAppPresenter.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.exempt.presenter;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Collections;
7 | import java.util.Comparator;
8 | import java.util.List;
9 | import java.util.Set;
10 |
11 | import io.github.trojan_gfw.igniter.common.os.Task;
12 | import io.github.trojan_gfw.igniter.common.os.Threads;
13 | import io.github.trojan_gfw.igniter.exempt.contract.ExemptAppContract;
14 | import io.github.trojan_gfw.igniter.exempt.data.AppInfo;
15 | import io.github.trojan_gfw.igniter.exempt.data.ExemptAppDataSource;
16 |
17 | public class ExemptAppPresenter implements ExemptAppContract.Presenter {
18 | private final ExemptAppContract.View mView;
19 | private final ExemptAppDataSource mDataSource;
20 | private boolean mDirty;
21 | private boolean mConfigurationChanged;
22 | private List mAllAppInfoList;
23 | private Set mExemptAppPackageNameSet;
24 |
25 | public ExemptAppPresenter(ExemptAppContract.View view, ExemptAppDataSource dataSource) {
26 | super();
27 | mView = view;
28 | mDataSource = dataSource;
29 | view.setPresenter(this);
30 | }
31 |
32 | @Override
33 | public void updateAppInfo(AppInfo appInfo, int position, boolean exempt) {
34 | mDirty = true;
35 | String packageName = appInfo.getPackageName();
36 | if (mExemptAppPackageNameSet.contains(packageName)) {
37 | if (!exempt) {
38 | mExemptAppPackageNameSet.remove(packageName);
39 | }
40 | } else if (exempt) {
41 | mExemptAppPackageNameSet.add(packageName);
42 | }
43 | appInfo.setExempt(exempt);
44 | }
45 |
46 | @Override
47 | public void filterAppsByName(final String name) {
48 | if (TextUtils.isEmpty(name)) {
49 | mView.showAppList(mAllAppInfoList);
50 | return;
51 | }
52 | Threads.instance().runOnWorkThread(new Task() {
53 | @Override
54 | public void onRun() {
55 | final List tmpInfoList = new ArrayList<>();
56 | final String lowercaseName = name.toLowerCase();
57 | for (AppInfo appInfo : mAllAppInfoList) {
58 | if (appInfo.getAppNameInLowercase().contains(lowercaseName)) {
59 | tmpInfoList.add(appInfo);
60 | }
61 | }
62 | Threads.instance().runOnUiThread(new Runnable() {
63 | @Override
64 | public void run() {
65 | mView.showAppList(tmpInfoList);
66 | }
67 | });
68 | }
69 | });
70 | }
71 |
72 | @Override
73 | public void saveExemptAppInfoList() {
74 | if (!mDirty) {
75 | mView.showSaveSuccess();
76 | return;
77 | }
78 | mConfigurationChanged = true;
79 | mView.showLoading();
80 | Threads.instance().runOnWorkThread(new Task() {
81 | @Override
82 | public void onRun() {
83 | mDataSource.saveExemptAppInfoSet(mExemptAppPackageNameSet);
84 | mDirty = false;
85 | Threads.instance().runOnUiThread(new Runnable() {
86 | @Override
87 | public void run() {
88 | mView.dismissLoading();
89 | mView.showSaveSuccess();
90 | }
91 | });
92 | }
93 | });
94 | }
95 |
96 | @Override
97 | public boolean handleBackPressed() {
98 | if (mDirty) {
99 | mView.showExitConfirm();
100 | }
101 | return mDirty;
102 | }
103 |
104 | @Override
105 | public void exit() {
106 | mView.exit(mConfigurationChanged);
107 | }
108 |
109 | @Override
110 | public void start() {
111 | mView.showLoading();
112 | Threads.instance().runOnWorkThread(new Task() {
113 | @Override
114 | public void onRun() {
115 | final List allAppInfoList = mDataSource.getAllAppInfoList();
116 | mExemptAppPackageNameSet = mDataSource.loadExemptAppPackageNameSet();
117 | for (AppInfo appInfo : allAppInfoList) {
118 | if (mExemptAppPackageNameSet.contains(appInfo.getPackageName())) {
119 | appInfo.setExempt(true);
120 | }
121 | }
122 | // cluster exempted apps.
123 | Collections.sort(allAppInfoList, new Comparator() {
124 | @Override
125 | public int compare(AppInfo o1, AppInfo o2) {
126 | if (o1.isExempt() != o2.isExempt()) {
127 | return o1.isExempt() ? -1 : 1;
128 | }
129 | return o1.getAppName().compareTo(o2.getAppName());
130 | }
131 | });
132 | mAllAppInfoList = allAppInfoList;
133 | Threads.instance().runOnUiThread(new Runnable() {
134 | @Override
135 | public void run() {
136 | mView.showAppList(allAppInfoList);
137 | mView.dismissLoading();
138 | }
139 | });
140 | }
141 | });
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/initializer/Initializer.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.initializer;
2 |
3 | import android.content.Context;
4 |
5 | public abstract class Initializer {
6 |
7 | public abstract void init(Context context);
8 |
9 | public abstract boolean runsInWorkerThread();
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/initializer/InitializerHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.initializer;
2 |
3 | import android.content.Context;
4 | import android.os.Process;
5 | import android.text.TextUtils;
6 |
7 | import java.util.LinkedList;
8 | import java.util.List;
9 |
10 | import io.github.trojan_gfw.igniter.common.os.Task;
11 | import io.github.trojan_gfw.igniter.common.os.Threads;
12 | import io.github.trojan_gfw.igniter.common.utils.ProcessUtils;
13 |
14 | /**
15 | * Helper class of application initializations. You can just extend {@link Initializer} to create your
16 | * own initializer and register it in {@link #registerMainInitializers()} or {@link #registerToolsInitializers()}.
17 | * You should consider carefully to determine which process your initializers are run in.
18 | */
19 | public class InitializerHelper {
20 | private static final String TOOL_PROCESS_POSTFIX = ":tools";
21 | private static final String PROXY_PROCESS_POSTFIX = ":proxy";
22 | private static List sInitializerList;
23 |
24 | private static void createInitializerList() {
25 | sInitializerList = new LinkedList<>();
26 | }
27 |
28 | private static void registerMainInitializers() {
29 | createInitializerList();
30 | sInitializerList.add(new MainInitializer());
31 | }
32 |
33 | private static void registerToolsInitializers() {
34 | createInitializerList();
35 | sInitializerList.add(new ToolInitializer());
36 | }
37 |
38 | private static void registerProxyInitializers() {
39 | createInitializerList();
40 | sInitializerList.add(new ProxyInitializer());
41 | }
42 |
43 | public static void runInit(Context context) {
44 | final String processName = ProcessUtils.getProcessNameByPID(context, Process.myPid());
45 | if (isToolProcess(processName)) {
46 | registerToolsInitializers();
47 | } else if (isProxyProcess(processName)) {
48 | registerProxyInitializers();
49 | } else {
50 | registerMainInitializers();
51 | }
52 | runInit(context, sInitializerList);
53 | clearInitializerLists();
54 | }
55 |
56 | private static void clearInitializerLists() {
57 | sInitializerList = null;
58 | }
59 |
60 | private static void runInit(final Context context, List initializerList) {
61 | final List runInWorkerThreadList = new LinkedList<>();
62 | for (int i = initializerList.size() - 1; i >= 0; i--) {
63 | if (initializerList.get(i).runsInWorkerThread()) {
64 | runInWorkerThreadList.add(initializerList.remove(i));
65 | }
66 | }
67 | Threads.instance().runOnWorkThread(new Task() {
68 | @Override
69 | public void onRun() {
70 | runInitList(context, runInWorkerThreadList);
71 | }
72 | });
73 | runInitList(context, initializerList);
74 | }
75 |
76 | private static void runInitList(Context context, List initializers) {
77 | for (int i = initializers.size() - 1; i >= 0; i--) {
78 | initializers.remove(i).init(context);
79 | }
80 | }
81 |
82 | private static boolean isMainProcess(String processName) {
83 | return !isToolProcess(processName) && !isProxyProcess(processName);
84 | }
85 |
86 | private static boolean isToolProcess(String processName) {
87 | return TextUtils.equals(processName, TOOL_PROCESS_POSTFIX);
88 | }
89 |
90 | private static boolean isProxyProcess(String processName) {
91 | return TextUtils.equals(processName, PROXY_PROCESS_POSTFIX);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/initializer/MainInitializer.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.initializer;
2 |
3 | import android.content.Context;
4 |
5 | import io.github.trojan_gfw.igniter.Globals;
6 |
7 | /**
8 | * Initializer that runs in Main Process (Default process).
9 | */
10 | public class MainInitializer extends Initializer {
11 |
12 | @Override
13 | public void init(Context context) {
14 | Globals.Init(context);
15 | }
16 |
17 | @Override
18 | public boolean runsInWorkerThread() {
19 | return false;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/initializer/ProxyInitializer.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.initializer;
2 |
3 | import android.content.Context;
4 |
5 | import io.github.trojan_gfw.igniter.Globals;
6 | import io.github.trojan_gfw.igniter.LogHelper;
7 | import io.github.trojan_gfw.igniter.TrojanConfig;
8 | import io.github.trojan_gfw.igniter.TrojanHelper;
9 |
10 | public class ProxyInitializer extends Initializer {
11 | private static final String TAG = "ProxyInitializer";
12 |
13 | @Override
14 | public void init(Context context) {
15 | Globals.Init(context);
16 | TrojanConfig cacheConfig = TrojanHelper.readTrojanConfig(Globals.getTrojanConfigPath());
17 | if (cacheConfig == null) {
18 | LogHelper.e(TAG, "read null trojan config");
19 | } else {
20 | cacheConfig.setCaCertPath(Globals.getCaCertPath());
21 | Globals.setTrojanConfigInstance(cacheConfig);
22 | }
23 | if (!Globals.getTrojanConfigInstance().isValidRunningConfig()) {
24 | LogHelper.e(TAG, "Invalid trojan config!");
25 | }
26 | }
27 |
28 | @Override
29 | public boolean runsInWorkerThread() {
30 | return false;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/initializer/ToolInitializer.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.initializer;
2 |
3 | import android.content.Context;
4 |
5 | import io.github.trojan_gfw.igniter.Globals;
6 |
7 | /**
8 | * Initializer that runs in Tools Process.
9 | */
10 | public class ToolInitializer extends Initializer {
11 |
12 | @Override
13 | public void init(Context context) {
14 | Globals.Init(context);
15 | }
16 |
17 | @Override
18 | public boolean runsInWorkerThread() {
19 | return false;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/qrcode/ScanQRCodeActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.qrcode;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.widget.Toast;
7 |
8 | import androidx.appcompat.app.AppCompatActivity;
9 |
10 | import cn.bingoogolapple.qrcode.core.BarcodeType;
11 | import cn.bingoogolapple.qrcode.zxing.ZXingView;
12 | import io.github.trojan_gfw.igniter.R;
13 |
14 | public class ScanQRCodeActivity extends AppCompatActivity implements ZXingView.Delegate {
15 | public static final String KEY_SCAN_CONTENT = "content";
16 | // private static final String TAG = "ScanQRCodeActivity";
17 |
18 | public static Intent create(Context context) {
19 | return new Intent(context, ScanQRCodeActivity.class);
20 | }
21 |
22 | private ZXingView mZXingView;
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_scan_qrcode);
27 | mZXingView = findViewById(R.id.zxingview);
28 | mZXingView.setType(BarcodeType.ONLY_QR_CODE, null);
29 | mZXingView.setDelegate(this);
30 | }
31 |
32 | @Override
33 | protected void onResume() {
34 | super.onResume();
35 | mZXingView.startCamera();
36 | mZXingView.startSpotAndShowRect();
37 | }
38 |
39 | @Override
40 | protected void onPause() {
41 | super.onPause();
42 | mZXingView.stopSpot();
43 | mZXingView.stopCamera();
44 | }
45 |
46 | @Override
47 | public void onScanQRCodeSuccess(String result) {
48 | Intent intent = new Intent();
49 | intent.putExtra(KEY_SCAN_CONTENT, result);
50 | setResult(RESULT_OK, intent);
51 | finish();
52 | }
53 |
54 | @Override
55 | public void onCameraAmbientBrightnessChanged(boolean isDark) {
56 |
57 | }
58 |
59 | @Override
60 | public void onScanQRCodeOpenCameraError() {
61 | runOnUiThread(new Runnable() {
62 | @Override
63 | public void run() {
64 | Toast.makeText(getApplicationContext(), R.string.scan_qr_code_camera_error, Toast.LENGTH_SHORT).show();
65 | }
66 | });
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/servers/activity/ServerListActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.servers.activity;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 |
7 | import androidx.fragment.app.FragmentManager;
8 |
9 | import io.github.trojan_gfw.igniter.Globals;
10 | import io.github.trojan_gfw.igniter.R;
11 | import io.github.trojan_gfw.igniter.common.app.BaseAppCompatActivity;
12 | import io.github.trojan_gfw.igniter.servers.data.ServerListDataManager;
13 | import io.github.trojan_gfw.igniter.servers.fragment.ServerListFragment;
14 | import io.github.trojan_gfw.igniter.servers.presenter.ServerListPresenter;
15 |
16 | public class ServerListActivity extends BaseAppCompatActivity {
17 | public static final String KEY_TROJAN_CONFIG = "trojan_config";
18 |
19 | public static Intent create(Context context) {
20 | return new Intent(context, ServerListActivity.class);
21 | }
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_server_list);
27 |
28 | FragmentManager fm = getSupportFragmentManager();
29 | ServerListFragment fragment = (ServerListFragment) fm.findFragmentByTag(ServerListFragment.TAG);
30 | if (fragment == null) {
31 | fragment = ServerListFragment.newInstance();
32 | }
33 | new ServerListPresenter(fragment, new ServerListDataManager(Globals.getTrojanConfigListPath()));
34 | fm.beginTransaction()
35 | .replace(R.id.parent_fl, fragment, ServerListFragment.TAG)
36 | .commitAllowingStateLoss();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/servers/contract/ServerListContract.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.servers.contract;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 |
6 | import java.util.List;
7 |
8 | import io.github.trojan_gfw.igniter.TrojanConfig;
9 | import io.github.trojan_gfw.igniter.common.mvp.BasePresenter;
10 | import io.github.trojan_gfw.igniter.common.mvp.BaseView;
11 |
12 | public interface ServerListContract {
13 | interface Presenter extends BasePresenter {
14 | void addServerConfig(String trojanUrl);
15 | void handleServerSelection(TrojanConfig config);
16 | void deleteServerConfig(TrojanConfig config, int pos);
17 | void gotoScanQRCode();
18 | void displayImportFileDescription();
19 | void hideImportFileDescription();
20 | void importConfigFromFile();
21 | void parseConfigsInFileStream(Context context, Uri fileUri);
22 | }
23 |
24 | interface View extends BaseView {
25 | void showAddTrojanConfigSuccess();
26 | void showQRCodeScanError(String scanContent);
27 | void selectServerConfig(TrojanConfig config);
28 | void showServerConfigList(List configs);
29 | void removeServerConfig(TrojanConfig config, int pos);
30 | void gotoScanQRCode();
31 | void showImportFileDescription();
32 | void dismissImportFileDescription();
33 | void openFileChooser();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/servers/data/ServerListDataManager.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.servers.data;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import io.github.trojan_gfw.igniter.TrojanConfig;
9 | import io.github.trojan_gfw.igniter.TrojanHelper;
10 |
11 | public class ServerListDataManager implements ServerListDataSource {
12 | private final String mConfigFilePath;
13 |
14 | public ServerListDataManager(String configFilePath) {
15 | mConfigFilePath = configFilePath;
16 | }
17 |
18 | @Override
19 | @NonNull
20 | public List loadServerConfigList() {
21 | return new ArrayList<>(TrojanHelper.readTrojanServerConfigList(mConfigFilePath));
22 | }
23 |
24 | @Override
25 | public void deleteServerConfig(TrojanConfig config) {
26 | List trojanConfigs = loadServerConfigList();
27 | for (int i = trojanConfigs.size() - 1; i >= 0; i--) {
28 | if (trojanConfigs.get(i).getRemoteAddr().equals(config.getRemoteAddr())) {
29 | trojanConfigs.remove(i);
30 | replaceServerConfigs(trojanConfigs);
31 | break;
32 | }
33 | }
34 | }
35 |
36 | @Override
37 | public void saveServerConfig(TrojanConfig config) {
38 | if (config == null) {
39 | return;
40 | }
41 | final String remoteAddr = config.getRemoteAddr();
42 | if (remoteAddr == null) {
43 | return;
44 | }
45 | boolean configRemoteAddrExists = false;
46 | List trojanConfigs = loadServerConfigList();
47 | for (int i = trojanConfigs.size() - 1; i >= 0; i--) {
48 | TrojanConfig cacheConfig = trojanConfigs.get(i);
49 | if (cacheConfig == null) continue;
50 | if (remoteAddr.equals(cacheConfig.getRemoteAddr())) {
51 | trojanConfigs.set(i, config);
52 | configRemoteAddrExists = true;
53 | break;
54 | }
55 | }
56 | if (!configRemoteAddrExists) {
57 | trojanConfigs.add(config);
58 | }
59 | replaceServerConfigs(trojanConfigs);
60 | }
61 |
62 | @Override
63 | public void replaceServerConfigs(List list) {
64 | TrojanHelper.writeTrojanServerConfigList(list, mConfigFilePath);
65 | TrojanHelper.ShowTrojanConfigList(mConfigFilePath);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/servers/data/ServerListDataSource.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.servers.data;
2 |
3 | import androidx.annotation.WorkerThread;
4 |
5 | import java.util.List;
6 |
7 | import io.github.trojan_gfw.igniter.TrojanConfig;
8 |
9 | public interface ServerListDataSource {
10 | @WorkerThread
11 | List loadServerConfigList();
12 | @WorkerThread
13 | void deleteServerConfig(TrojanConfig config);
14 | @WorkerThread
15 | void saveServerConfig(TrojanConfig config);
16 | @WorkerThread
17 | void replaceServerConfigs(List list);
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/servers/fragment/ServerListAdapter.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.servers.fragment;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import io.github.trojan_gfw.igniter.R;
16 | import io.github.trojan_gfw.igniter.TrojanConfig;
17 |
18 | public class ServerListAdapter extends RecyclerView.Adapter {
19 | private final LayoutInflater mInflater;
20 | private final List mData;
21 | private OnItemClickListener mOnItemClickListener;
22 |
23 | public ServerListAdapter(Context context, List data) {
24 | super();
25 | this.mData = new ArrayList<>(data);
26 | mInflater = LayoutInflater.from(context);
27 | }
28 |
29 | @NonNull
30 | @Override
31 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
32 | ViewHolder vh = new ViewHolder(mInflater.inflate(R.layout.item_server, viewGroup, false));
33 | vh.bindListener(mOnItemClickListener);
34 | return vh;
35 | }
36 |
37 | public void replaceData(List data) {
38 | mData.clear();
39 | mData.addAll(data);
40 | notifyDataSetChanged();
41 | }
42 |
43 | public void removeItemOnPosition(int pos) {
44 | mData.remove(pos);
45 | notifyItemRemoved(pos);
46 | }
47 |
48 | @Override
49 | public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
50 | viewHolder.bindData(mData.get(i));
51 | }
52 |
53 | @Override
54 | public int getItemCount() {
55 | return mData.size();
56 | }
57 |
58 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
59 | this.mOnItemClickListener = onItemClickListener;
60 | }
61 |
62 | public interface OnItemClickListener {
63 | void onItemSelected(TrojanConfig config, int pos);
64 |
65 | void onItemDelete(TrojanConfig config, int pos);
66 | }
67 | }
68 |
69 | class ViewHolder extends RecyclerView.ViewHolder {
70 | private TrojanConfig mConfig;
71 | private TextView mRemoteAddrTv;
72 | private ServerListAdapter.OnItemClickListener itemClickListener;
73 |
74 | public ViewHolder(@NonNull final View itemView) {
75 | super(itemView);
76 | mRemoteAddrTv = itemView.findViewById(R.id.serverAddrTv);
77 | itemView.setOnClickListener(new View.OnClickListener() {
78 | @Override
79 | public void onClick(View v) {
80 | if (itemClickListener != null) {
81 | itemClickListener.onItemSelected(mConfig, getBindingAdapterPosition());
82 | }
83 | }
84 | });
85 | itemView.findViewById(R.id.deleteServerBtn).setOnClickListener(new View.OnClickListener() {
86 | @Override
87 | public void onClick(View v) {
88 | if (itemClickListener != null) {
89 | itemClickListener.onItemDelete(mConfig, getBindingAdapterPosition());
90 | }
91 | }
92 | });
93 | }
94 |
95 | public void bindData(TrojanConfig config) {
96 | this.mConfig = config;
97 | mRemoteAddrTv.setText(config.getRemoteAddr());
98 | }
99 |
100 | public void bindListener(ServerListAdapter.OnItemClickListener listener) {
101 | itemClickListener = listener;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/servers/fragment/ServerListFragment.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.servers.fragment;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.Dialog;
6 | import android.content.Context;
7 | import android.content.DialogInterface;
8 | import android.content.Intent;
9 | import android.content.pm.PackageManager;
10 | import android.graphics.drawable.Drawable;
11 | import android.net.Uri;
12 | import android.os.Bundle;
13 | import android.view.LayoutInflater;
14 | import android.view.Menu;
15 | import android.view.MenuInflater;
16 | import android.view.MenuItem;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 | import android.widget.Toast;
20 |
21 | import androidx.annotation.NonNull;
22 | import androidx.annotation.Nullable;
23 | import androidx.appcompat.app.AlertDialog;
24 | import androidx.appcompat.app.AppCompatActivity;
25 | import androidx.appcompat.widget.Toolbar;
26 | import androidx.core.content.ContextCompat;
27 | import androidx.core.graphics.drawable.DrawableCompat;
28 | import androidx.fragment.app.FragmentActivity;
29 | import androidx.recyclerview.widget.LinearLayoutManager;
30 | import androidx.recyclerview.widget.RecyclerView;
31 |
32 | import java.util.ArrayList;
33 | import java.util.List;
34 |
35 | import io.github.trojan_gfw.igniter.R;
36 | import io.github.trojan_gfw.igniter.TrojanConfig;
37 | import io.github.trojan_gfw.igniter.common.app.BaseFragment;
38 | import io.github.trojan_gfw.igniter.qrcode.ScanQRCodeActivity;
39 | import io.github.trojan_gfw.igniter.servers.activity.ServerListActivity;
40 | import io.github.trojan_gfw.igniter.servers.contract.ServerListContract;
41 |
42 | public class ServerListFragment extends BaseFragment implements ServerListContract.View {
43 | private static final int FILE_IMPORT_REQUEST_CODE = 120;
44 | private static final int SCAN_QR_CODE_REQUEST_CODE = 110;
45 | private static final int REQUEST_CAMERA_CODE = 114;
46 | public static final String TAG = "ServerListFragment";
47 | public static final String KEY_TROJAN_CONFIG = ServerListActivity.KEY_TROJAN_CONFIG;
48 | private ServerListContract.Presenter mPresenter;
49 | private RecyclerView mServerListRv;
50 | private ServerListAdapter mServerListAdapter;
51 | private Dialog mImportConfigDialog;
52 |
53 | public ServerListFragment() {
54 | // Required empty public constructor
55 | }
56 |
57 | public static ServerListFragment newInstance() {
58 | return new ServerListFragment();
59 | }
60 |
61 | @Override
62 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
63 | Bundle savedInstanceState) {
64 | // Inflate the layout for this fragment
65 | return inflater.inflate(R.layout.fragment_server_list, container, false);
66 | }
67 |
68 | @Override
69 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
70 | super.onViewCreated(view, savedInstanceState);
71 | findViews();
72 | initViews();
73 | initListeners();
74 | mPresenter.start();
75 | }
76 |
77 | private void findViews() {
78 | mServerListRv = findViewById(R.id.serverListRv);
79 | }
80 |
81 | private void initViews() {
82 | FragmentActivity activity = getActivity();
83 | if (activity instanceof AppCompatActivity) {
84 | ((AppCompatActivity) activity).setSupportActionBar((Toolbar)findViewById(R.id.toolbar));
85 | setHasOptionsMenu(true);
86 | }
87 | mServerListRv.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));
88 | mServerListAdapter = new ServerListAdapter(getContext(), new ArrayList());
89 | mServerListRv.setAdapter(mServerListAdapter);
90 | }
91 |
92 | private void initListeners() {
93 | mServerListAdapter.setOnItemClickListener(new ServerListAdapter.OnItemClickListener() {
94 | @Override
95 | public void onItemSelected(TrojanConfig config, int pos) {
96 | mPresenter.handleServerSelection(config);
97 | }
98 |
99 | @Override
100 | public void onItemDelete(TrojanConfig config, int pos) {
101 | mPresenter.deleteServerConfig(config, pos);
102 | }
103 | });
104 | }
105 |
106 | private Context getApplicationContext() {
107 | if (getActivity() != null) {
108 | return getActivity().getApplicationContext();
109 | }
110 | return null;
111 | }
112 |
113 | @Override
114 | public void showAddTrojanConfigSuccess() {
115 | mRootView.post(new Runnable() {
116 | @Override
117 | public void run() {
118 | Toast.makeText(getApplicationContext(), R.string.scan_qr_code_success, Toast.LENGTH_SHORT).show();
119 | }
120 | });
121 | }
122 |
123 | @Override
124 | public void showQRCodeScanError(final String scanContent) {
125 | mRootView.post(new Runnable() {
126 | @Override
127 | public void run() {
128 | Toast.makeText(getApplicationContext(), getString(R.string.scan_qr_code_failed, scanContent), Toast.LENGTH_SHORT).show();
129 | }
130 | });
131 | }
132 |
133 | @Override
134 | public void gotoScanQRCode() {
135 | if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA)) {
136 | gotoScanQRCodeInner();
137 | } else {
138 | requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_CODE);
139 | }
140 | }
141 |
142 | private void gotoScanQRCodeInner() {
143 | startActivityForResult(ScanQRCodeActivity.create(mContext), SCAN_QR_CODE_REQUEST_CODE);
144 | }
145 |
146 | @Override
147 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
148 | super.onActivityResult(requestCode, resultCode, data);
149 | if (SCAN_QR_CODE_REQUEST_CODE == requestCode && resultCode == Activity.RESULT_OK && data != null) {
150 | mPresenter.addServerConfig(data.getStringExtra(ScanQRCodeActivity.KEY_SCAN_CONTENT));
151 | } else if (FILE_IMPORT_REQUEST_CODE == requestCode && resultCode == Activity.RESULT_OK && data != null) {
152 | Uri uri = data.getData();
153 | if (uri != null) {
154 | mPresenter.parseConfigsInFileStream(getContext(), uri);
155 | }
156 | }
157 | }
158 |
159 | @Override
160 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
161 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
162 | if (REQUEST_CAMERA_CODE == requestCode) {
163 | if (PackageManager.PERMISSION_GRANTED == grantResults[0]) {
164 | gotoScanQRCodeInner();
165 | } else {
166 | Toast.makeText(getContext(), R.string.server_list_lack_of_camera_permission, Toast.LENGTH_SHORT).show();
167 | }
168 | }
169 | }
170 |
171 | @Override
172 | public void selectServerConfig(TrojanConfig config) {
173 | FragmentActivity activity = getActivity();
174 | if (activity != null) {
175 | Intent intent = new Intent();
176 | intent.putExtra(KEY_TROJAN_CONFIG, config);
177 | activity.setResult(Activity.RESULT_OK, intent);
178 | activity.finish();
179 | }
180 | }
181 |
182 |
183 |
184 | @Override
185 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
186 | menu.clear();
187 | inflater.inflate(R.menu.menu_server_list, menu);
188 | MenuItem item = menu.getItem(0);
189 | // Tint scan QRCode icon to white.
190 | if (item.getIcon() != null) {
191 | Drawable drawable = item.getIcon();
192 | Drawable wrapper = DrawableCompat.wrap(drawable);
193 | drawable.mutate();
194 | DrawableCompat.setTint(wrapper, ContextCompat.getColor(mContext, android.R.color.white));
195 | item.setIcon(drawable);
196 | }
197 | }
198 |
199 | @Override
200 | public boolean onOptionsItemSelected(MenuItem item) {
201 | switch (item.getItemId()) {
202 | case R.id.action_scan_qr_code:
203 | mPresenter.gotoScanQRCode();
204 | return true;
205 | case R.id.action_import_from_file:
206 | mPresenter.displayImportFileDescription();
207 | return true;
208 | default:
209 | break;
210 | }
211 | return super.onOptionsItemSelected(item);
212 | }
213 |
214 | @Override
215 | public void showImportFileDescription() {
216 | mImportConfigDialog = new AlertDialog.Builder(mContext).setTitle(R.string.common_alert)
217 | .setMessage(R.string.server_list_import_file_desc)
218 | .setPositiveButton(R.string.common_confirm, new DialogInterface.OnClickListener() {
219 | @Override
220 | public void onClick(DialogInterface dialog, int which) {
221 | mPresenter.importConfigFromFile();
222 | }
223 | }).setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
224 | @Override
225 | public void onClick(DialogInterface dialog, int which) {
226 | mPresenter.hideImportFileDescription();
227 | }
228 | }).create();
229 | mImportConfigDialog.show();
230 | }
231 |
232 | @Override
233 | public void dismissImportFileDescription() {
234 | if (mImportConfigDialog != null && mImportConfigDialog.isShowing()) {
235 | mImportConfigDialog.dismiss();
236 | mImportConfigDialog = null;
237 | }
238 | }
239 |
240 | @Override
241 | public void openFileChooser() {
242 | Intent intent = new Intent()
243 | .setType("text/plain")
244 | .setAction(Intent.ACTION_GET_CONTENT);
245 | startActivityForResult(Intent.createChooser(intent, getString(R.string.server_list_file_chooser_msg)), FILE_IMPORT_REQUEST_CODE);
246 | }
247 |
248 | @Override
249 | public void showServerConfigList(final List configs) {
250 | mRootView.post(new Runnable() {
251 | @Override
252 | public void run() {
253 | mServerListAdapter.replaceData(configs);
254 | }
255 | });
256 | }
257 |
258 | @Override
259 | public void removeServerConfig(TrojanConfig config, final int pos) {
260 | mRootView.post(new Runnable() {
261 | @Override
262 | public void run() {
263 | mServerListAdapter.removeItemOnPosition(pos);
264 | }
265 | });
266 | }
267 |
268 | @Override
269 | public void setPresenter(ServerListContract.Presenter presenter) {
270 | mPresenter = presenter;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/servers/presenter/ServerListPresenter.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.servers.presenter;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 |
6 | import org.json.JSONArray;
7 | import org.json.JSONException;
8 | import org.json.JSONObject;
9 |
10 | import java.io.BufferedReader;
11 | import java.io.IOException;
12 | import java.io.InputStreamReader;
13 | import java.util.ArrayList;
14 | import java.util.Collections;
15 | import java.util.HashSet;
16 | import java.util.List;
17 | import java.util.Set;
18 |
19 | import io.github.trojan_gfw.igniter.TrojanConfig;
20 | import io.github.trojan_gfw.igniter.TrojanURLHelper;
21 | import io.github.trojan_gfw.igniter.common.os.Task;
22 | import io.github.trojan_gfw.igniter.common.os.Threads;
23 | import io.github.trojan_gfw.igniter.servers.contract.ServerListContract;
24 | import io.github.trojan_gfw.igniter.servers.data.ServerListDataSource;
25 |
26 | public class ServerListPresenter implements ServerListContract.Presenter {
27 | private final ServerListContract.View mView;
28 | private final ServerListDataSource mDataManager;
29 |
30 | public ServerListPresenter(ServerListContract.View view, ServerListDataSource dataManager) {
31 | mView = view;
32 | mDataManager = dataManager;
33 | view.setPresenter(this);
34 | }
35 |
36 | @Override
37 | public void hideImportFileDescription() {
38 | mView.dismissImportFileDescription();
39 | }
40 |
41 | @Override
42 | public void displayImportFileDescription() {
43 | mView.showImportFileDescription();
44 | }
45 |
46 | @Override
47 | public void importConfigFromFile() {
48 | mView.openFileChooser();
49 | }
50 |
51 | @Override
52 | public void parseConfigsInFileStream(final Context context, final Uri fileUri) {
53 | Threads.instance().runOnWorkThread(new Task() {
54 | @Override
55 | public void onRun() {
56 | StringBuilder sb = new StringBuilder();
57 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(context.getContentResolver().openInputStream(fileUri)))) {
58 | String line;
59 | while ((line = reader.readLine()) != null) {
60 | sb.append(line);
61 | }
62 | } catch (IOException e) {
63 | e.printStackTrace();
64 | }
65 | List trojanConfigs = parseTrojanConfigsFromFileContent(sb.toString());
66 | List currentConfigs = mDataManager.loadServerConfigList();
67 | currentConfigs.addAll(trojanConfigs);
68 | // remove repeated configurations
69 | Set newTrojanConfigRemoteAddrSet = new HashSet<>();
70 | for (TrojanConfig config : trojanConfigs) {
71 | newTrojanConfigRemoteAddrSet.add(config.getRemoteAddr());
72 | }
73 | for (int i = currentConfigs.size() - 1; i >= 0; i--) {
74 | if (newTrojanConfigRemoteAddrSet.contains(currentConfigs.get(i).getRemoteAddr())) {
75 | currentConfigs.remove(i);
76 | }
77 | }
78 | currentConfigs.addAll(trojanConfigs);
79 | mDataManager.replaceServerConfigs(currentConfigs);
80 | loadConfigs();
81 | mView.showAddTrojanConfigSuccess();
82 | }
83 | });
84 | }
85 |
86 | private List parseTrojanConfigsFromFileContent(String fileContent) {
87 | try {
88 | JSONObject jsonObject = new JSONObject(fileContent);
89 | JSONArray configs = jsonObject.optJSONArray("configs");
90 | if (configs == null) {
91 | return Collections.emptyList();
92 | }
93 | final int len = configs.length();
94 | List list = new ArrayList<>(len);
95 | for (int i = 0; i < len; i++) {
96 | JSONObject config = configs.getJSONObject(i);
97 | String remoteAddr = config.optString("server", null);
98 | if (remoteAddr == null) {
99 | continue;
100 | }
101 | TrojanConfig tmp = new TrojanConfig();
102 | tmp.setRemoteAddr(remoteAddr);
103 | tmp.setRemotePort(config.optInt("server_port"));
104 | tmp.setPassword(config.optString("password"));
105 | tmp.setVerifyCert(config.optBoolean("verify"));
106 | list.add(tmp);
107 | }
108 | return list;
109 | } catch (JSONException e) {
110 | e.printStackTrace();
111 | }
112 | return Collections.emptyList();
113 | }
114 |
115 | @Override
116 | public void addServerConfig(final String trojanUrl) {
117 | Threads.instance().runOnWorkThread(new Task() {
118 | @Override
119 | public void onRun() {
120 | TrojanConfig config = TrojanURLHelper.ParseTrojanURL(trojanUrl);
121 | if (config != null) {
122 | mDataManager.saveServerConfig(config);
123 | loadConfigs();
124 | mView.showAddTrojanConfigSuccess();
125 | } else {
126 | mView.showQRCodeScanError(trojanUrl);
127 | }
128 | }
129 | });
130 | }
131 |
132 | @Override
133 | public void handleServerSelection(TrojanConfig config) {
134 | mView.selectServerConfig(config);
135 | }
136 |
137 | @Override
138 | public void deleteServerConfig(final TrojanConfig config, final int pos) {
139 | Threads.instance().runOnWorkThread(new Task() {
140 | @Override
141 | public void onRun() {
142 | mDataManager.deleteServerConfig(config);
143 | mView.removeServerConfig(config, pos);
144 | }
145 | });
146 | }
147 |
148 | @Override
149 | public void start() {
150 | Threads.instance().runOnWorkThread(new Task() {
151 | @Override
152 | public void onRun() {
153 | loadConfigs();
154 | }
155 | });
156 | }
157 |
158 | @Override
159 | public void gotoScanQRCode() {
160 | mView.gotoScanQRCode();
161 | }
162 |
163 | private void loadConfigs() {
164 | List trojanConfigs = mDataManager.loadServerConfigList();
165 | mView.showServerConfigList(trojanConfigs);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/tile/IgniterTileService.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.tile;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Build;
6 | import android.os.RemoteException;
7 | import android.service.quicksettings.Tile;
8 | import android.service.quicksettings.TileService;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.RequiresApi;
12 |
13 | import io.github.trojan_gfw.igniter.LogHelper;
14 | import io.github.trojan_gfw.igniter.MainActivity;
15 | import io.github.trojan_gfw.igniter.ProxyService;
16 | import io.github.trojan_gfw.igniter.common.constants.Constants;
17 | import io.github.trojan_gfw.igniter.common.utils.PreferenceUtils;
18 | import io.github.trojan_gfw.igniter.connection.TrojanConnection;
19 | import io.github.trojan_gfw.igniter.proxy.aidl.ITrojanService;
20 |
21 | /**
22 | * Igniter's implementation of TileService, showing current state of {@link ProxyService} and providing a
23 | * shortcut to start or stop {@link ProxyService} by the help of {@link ProxyHelper}. This
24 | * service receives state change by the help of {@link TrojanConnection}.
25 | *
26 | * @see ProxyService
27 | * @see io.github.trojan_gfw.igniter.ProxyService.ProxyState
28 | */
29 | @RequiresApi(api = Build.VERSION_CODES.N)
30 | public class IgniterTileService extends TileService implements TrojanConnection.Callback {
31 | private static final String TAG = "IgniterTile";
32 | private final TrojanConnection mConnection = new TrojanConnection(false);
33 | /**
34 | * Indicates that user had tapped the tile before {@link TrojanConnection} connects {@link ProxyService}.
35 | * Generally speaking, when the connection is built, we should call {@link #onClick()} again if
36 | * the value is true
.
37 | */
38 | private boolean mTapPending;
39 |
40 | @Override
41 | public void onStartListening() {
42 | super.onStartListening();
43 | LogHelper.i(TAG, "onStartListening");
44 | mConnection.connect(this, this);
45 | }
46 |
47 | @Override
48 | public void onStopListening() {
49 | super.onStopListening();
50 | LogHelper.i(TAG, "onStopListening");
51 | mConnection.disconnect(this);
52 | }
53 |
54 | @Override
55 | public void onServiceConnected(ITrojanService service) {
56 | LogHelper.i(TAG, "onServiceConnected");
57 | try {
58 | int state = service.getState();
59 | updateTile(state);
60 | if (mTapPending) {
61 | mTapPending = false;
62 | onClick();
63 | }
64 | } catch (RemoteException e) {
65 | e.printStackTrace();
66 | }
67 | }
68 |
69 | @Override
70 | public void onServiceDisconnected() {
71 | LogHelper.i(TAG, "onServiceDisconnected");
72 | }
73 |
74 | @Override
75 | public void onStateChanged(int state, String msg) {
76 | LogHelper.i(TAG, "onStateChanged# state: " + state + ", msg: " + msg);
77 | updateTile(state);
78 | }
79 |
80 | @Override
81 | public void onTestResult(String testUrl, boolean connected, long delay, @NonNull String error) {
82 | // Do nothing, since TileService will not submit test request.
83 | }
84 |
85 | @Override
86 | public void onBinderDied() {
87 | LogHelper.i(TAG, "onBinderDied");
88 | }
89 |
90 | private void updateTile(final @ProxyService.ProxyState int state) {
91 | Tile tile = getQsTile();
92 | if (tile == null) {
93 | return;
94 | }
95 | LogHelper.i(TAG, "updateTile with state: " + state);
96 | switch (state) {
97 | case ProxyService.STATE_NONE:
98 | tile.setState(Tile.STATE_INACTIVE);
99 | break;
100 | case ProxyService.STOPPED:
101 | break;
102 | case ProxyService.STARTED:
103 | tile.setState(Tile.STATE_ACTIVE);
104 | break;
105 | case ProxyService.STARTING:
106 | case ProxyService.STOPPING:
107 | tile.setState(Tile.STATE_UNAVAILABLE);
108 | break;
109 | default:
110 | LogHelper.e(TAG, "Unknown state: " + state);
111 | break;
112 | }
113 | tile.updateTile();
114 | }
115 |
116 | private boolean isFirstStart() {
117 | return PreferenceUtils.getBooleanPreference(getContentResolver(), Uri.parse(Constants.PREFERENCE_URI),
118 | Constants.PREFERENCE_KEY_FIRST_START, true);
119 | }
120 |
121 | @Override
122 | public void onClick() {
123 | super.onClick();
124 | LogHelper.i(TAG, "onClick");
125 | if (isFirstStart()) {
126 | // if user never open Igniter before, when he/she clicks the tile, it is necessary
127 | // to start the launcher activity for resource preparation.
128 | Intent intent = new Intent(this, MainActivity.class);
129 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
130 | startActivity(intent);
131 | return;
132 | }
133 | ITrojanService service = mConnection.getService();
134 | if (service == null) {
135 | mTapPending = true;
136 | updateTile(ProxyService.STARTING);
137 | } else {
138 | try {
139 | @ProxyService.ProxyState int state = service.getState();
140 | updateTile(state);
141 | switch (state) {
142 | case ProxyService.STARTED:
143 | stopProxyService();
144 | break;
145 | case ProxyService.STARTING:
146 | case ProxyService.STOPPING:
147 | break;
148 | case ProxyService.STATE_NONE:
149 | case ProxyService.STOPPED:
150 | startProxyService();
151 | break;
152 | default:
153 | LogHelper.e(TAG, "Unknown state: " + state);
154 | break;
155 | }
156 | } catch (RemoteException e) {
157 | e.printStackTrace();
158 | }
159 | }
160 | }
161 |
162 | /**
163 | * Start ProxyService if everything is ready. Otherwise start the launcher Activity.
164 | */
165 | private void startProxyService() {
166 | if (ProxyHelper.isTrojanConfigValid() && ProxyHelper.isVPNServiceConsented(this)) {
167 | ProxyHelper.startProxyService(this);
168 | } else {
169 | ProxyHelper.startLauncherActivity(this);
170 | }
171 | }
172 |
173 | private void stopProxyService() {
174 | ProxyHelper.stopProxyService(this);
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/tile/ProxyHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.tile;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.VpnService;
6 |
7 | import androidx.core.content.ContextCompat;
8 |
9 | import io.github.trojan_gfw.igniter.BuildConfig;
10 | import io.github.trojan_gfw.igniter.Globals;
11 | import io.github.trojan_gfw.igniter.MainActivity;
12 | import io.github.trojan_gfw.igniter.ProxyService;
13 | import io.github.trojan_gfw.igniter.R;
14 | import io.github.trojan_gfw.igniter.TrojanConfig;
15 | import io.github.trojan_gfw.igniter.TrojanHelper;
16 |
17 | /**
18 | * Helper class for starting or stopping {@link ProxyService}. Before starting {@link ProxyService},
19 | * make sure the TrojanConfig is valid (with the help of {@link #isTrojanConfigValid()} and whether
20 | * user has consented VPN Service (with the help of {@link #isVPNServiceConsented(Context)}.
21 | *
22 | * It's recommended to start launcher activity when the config is invalid or user hasn't consented
23 | * VPN service.
24 | */
25 | public abstract class ProxyHelper {
26 | public static boolean isTrojanConfigValid() {
27 | TrojanConfig cacheConfig = TrojanHelper.readTrojanConfig(Globals.getTrojanConfigPath());
28 | if (cacheConfig == null) {
29 | return false;
30 | }
31 | cacheConfig.setCaCertPath(Globals.getCaCertPath());
32 | if (BuildConfig.DEBUG) {
33 | TrojanHelper.ShowConfig(Globals.getTrojanConfigPath());
34 | }
35 | return cacheConfig.isValidRunningConfig();
36 | }
37 |
38 | public static boolean isVPNServiceConsented(Context context) {
39 | return VpnService.prepare(context.getApplicationContext()) == null;
40 | }
41 |
42 | public static void startProxyService(Context context) {
43 | ContextCompat.startForegroundService(context, new Intent(context, ProxyService.class));
44 | }
45 |
46 | public static void startLauncherActivity(Context context) {
47 | Intent intent = new Intent(context, MainActivity.class);
48 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
49 | context.startActivity(intent);
50 | }
51 |
52 | public static void stopProxyService(Context context) {
53 | Intent intent = new Intent(context.getString(R.string.stop_service));
54 | intent.setPackage(context.getPackageName());
55 | context.sendBroadcast(intent);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-hdpi/ic_action_link.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-hdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-hdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-hdpi/ic_search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-hdpi/ic_tile.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-hdpi/qr_code.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-mdpi/ic_action_link.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-mdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-mdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-mdpi/ic_search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-mdpi/ic_tile.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-mdpi/qr_code.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xhdpi/ic_action_link.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xhdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xhdpi/ic_search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xhdpi/ic_tile.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xhdpi/qr_code.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxhdpi/ic_action_link.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxhdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxhdpi/ic_search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxhdpi/ic_tile.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxhdpi/qr_code.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxxhdpi/ic_action_link.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxxhdpi/ic_action_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxxhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxxhdpi/ic_search.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxxhdpi/ic_tile.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable-xxxhdpi/qr_code.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/common_round_rect_white_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/drawable/qr_code.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_exempt_app.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
24 |
25 |
34 |
35 |
50 |
51 |
68 |
69 |
84 |
85 |
97 |
98 |
112 |
113 |
125 |
126 |
136 |
137 |
143 |
144 |
145 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_scan_qrcode.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_server_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exempt_app.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_server_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_app_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_server.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_exempt_app.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_server_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/country.mmdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/app/src/main/res/raw/country.mmdb
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 1.0.0.0/8
5 | - 2.0.0.0/7
6 | - 4.0.0.0/6
7 | - 8.0.0.0/7
8 | - 11.0.0.0/8
9 | - 12.0.0.0/6
10 | - 16.0.0.0/4
11 | - 32.0.0.0/3
12 | - 64.0.0.0/3
13 | - 96.0.0.0/6
14 | - 100.0.0.0/10
15 | - 100.128.0.0/9
16 | - 101.0.0.0/8
17 | - 102.0.0.0/7
18 | - 104.0.0.0/5
19 | - 112.0.0.0/10
20 | - 112.64.0.0/11
21 | - 112.96.0.0/12
22 | - 112.112.0.0/13
23 | - 112.120.0.0/14
24 | - 112.124.0.0/19
25 | - 112.124.32.0/21
26 | - 112.124.40.0/22
27 | - 112.124.44.0/23
28 | - 112.124.46.0/24
29 | - 112.124.48.0/20
30 | - 112.124.64.0/18
31 | - 112.124.128.0/17
32 | - 112.125.0.0/16
33 | - 112.126.0.0/15
34 | - 112.128.0.0/9
35 | - 113.0.0.0/8
36 | - 114.0.0.0/10
37 | - 114.64.0.0/11
38 | - 114.96.0.0/12
39 | - 114.112.0.0/15
40 | - 114.114.0.0/18
41 | - 114.114.64.0/19
42 | - 114.114.96.0/20
43 | - 114.114.112.0/23
44 | - 114.114.115.0/24
45 | - 114.114.116.0/22
46 | - 114.114.120.0/21
47 | - 114.114.128.0/17
48 | - 114.115.0.0/16
49 | - 114.116.0.0/14
50 | - 114.120.0.0/13
51 | - 114.128.0.0/9
52 | - 115.0.0.0/8
53 | - 116.0.0.0/6
54 | - 120.0.0.0/6
55 | - 124.0.0.0/7
56 | - 126.0.0.0/8
57 | - 128.0.0.0/3
58 | - 160.0.0.0/5
59 | - 168.0.0.0/8
60 | - 169.0.0.0/9
61 | - 169.128.0.0/10
62 | - 169.192.0.0/11
63 | - 169.224.0.0/12
64 | - 169.240.0.0/13
65 | - 169.248.0.0/14
66 | - 169.252.0.0/15
67 | - 169.255.0.0/16
68 | - 170.0.0.0/7
69 | - 172.0.0.0/12
70 | - 172.32.0.0/11
71 | - 172.64.0.0/10
72 | - 172.128.0.0/9
73 | - 173.0.0.0/8
74 | - 174.0.0.0/7
75 | - 176.0.0.0/4
76 | - 192.0.0.8/29
77 | - 192.0.0.16/28
78 | - 192.0.0.32/27
79 | - 192.0.0.64/26
80 | - 192.0.0.128/25
81 | - 192.0.1.0/24
82 | - 192.0.3.0/24
83 | - 192.0.4.0/22
84 | - 192.0.8.0/21
85 | - 192.0.16.0/20
86 | - 192.0.32.0/19
87 | - 192.0.64.0/18
88 | - 192.0.128.0/17
89 | - 192.1.0.0/16
90 | - 192.2.0.0/15
91 | - 192.4.0.0/14
92 | - 192.8.0.0/13
93 | - 192.16.0.0/12
94 | - 192.32.0.0/11
95 | - 192.64.0.0/12
96 | - 192.80.0.0/13
97 | - 192.88.0.0/18
98 | - 192.88.64.0/19
99 | - 192.88.96.0/23
100 | - 192.88.98.0/24
101 | - 192.88.100.0/22
102 | - 192.88.104.0/21
103 | - 192.88.112.0/20
104 | - 192.88.128.0/17
105 | - 192.89.0.0/16
106 | - 192.90.0.0/15
107 | - 192.92.0.0/14
108 | - 192.96.0.0/11
109 | - 192.128.0.0/11
110 | - 192.160.0.0/13
111 | - 192.169.0.0/16
112 | - 192.170.0.0/15
113 | - 192.172.0.0/14
114 | - 192.176.0.0/12
115 | - 192.192.0.0/10
116 | - 193.0.0.0/8
117 | - 194.0.0.0/7
118 | - 196.0.0.0/7
119 | - 198.0.0.0/12
120 | - 198.16.0.0/15
121 | - 198.20.0.0/14
122 | - 198.24.0.0/13
123 | - 198.32.0.0/12
124 | - 198.48.0.0/15
125 | - 198.50.0.0/16
126 | - 198.51.0.0/18
127 | - 198.51.64.0/19
128 | - 198.51.96.0/22
129 | - 198.51.101.0/24
130 | - 198.51.102.0/23
131 | - 198.51.104.0/21
132 | - 198.51.112.0/20
133 | - 198.51.128.0/17
134 | - 198.52.0.0/14
135 | - 198.56.0.0/13
136 | - 198.64.0.0/10
137 | - 198.128.0.0/9
138 | - 199.0.0.0/8
139 | - 200.0.0.0/7
140 | - 202.0.0.0/8
141 | - 203.0.0.0/18
142 | - 203.0.64.0/19
143 | - 203.0.96.0/20
144 | - 203.0.112.0/24
145 | - 203.0.114.0/23
146 | - 203.0.116.0/22
147 | - 203.0.120.0/21
148 | - 203.0.128.0/17
149 | - 203.1.0.0/16
150 | - 203.2.0.0/15
151 | - 203.4.0.0/14
152 | - 203.8.0.0/13
153 | - 203.16.0.0/12
154 | - 203.32.0.0/11
155 | - 203.64.0.0/10
156 | - 203.128.0.0/9
157 | - 204.0.0.0/6
158 | - 208.0.0.0/4
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 18sp
4 | 55dp
5 | 15dp
6 |
7 | 64dp
8 | 16dp
9 |
10 | 20dp
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #448AFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Igniter
3 |
4 | Delete
5 | Alert
6 | Confirm
7 | Cancel
8 | Save success
9 |
10 | Remote Address
11 | Remote Port
12 | 443
13 | Password
14 | Start
15 | Starting
16 | Stop
17 | Stopping
18 | Verify Certificate
19 | Exempt Chinese Domain/IPs
20 | Enable IPv6
21 | io.github.trojan_gfw.igniter.STOP_SERVICE
22 | io.github.trojan_gfw.igniter.BIND_SERVICE
23 | Connected to %1$s in %2$sms.
24 | Failed to connect to %1$s: %2$s
25 | Invalid Configuration
26 | igniter_notify_chan
27 | Igniter notifications
28 | Starting service
29 | Restart proxy yourself to apply new exempt apps configuration.
30 | Do you want to import the Trojan URL you just copied?
31 |
32 |
33 | Test Connection
34 | Show develop info in logcat
35 |
36 | Import config in file extension (*.txt)
37 | Select a TXT file
38 | Import from file
39 | @string/common_delete
40 | Lack of camera permission!
41 | @string/common_save_success
42 | Save failed
43 |
44 |
45 | Igniter needs to read and write external storage for reading or writing exempted app
46 | list, which is recoverable even when Igniter is uninstalled. Denying the permission
47 | will deactivate exempted app feature.
48 |
49 |
50 | Please grant read and write external storage permission in Igniter application settings first.
51 |
52 | Load Server
53 |
54 | Add server success
55 | Cannot recognize trojanURL: %s
56 | Scan QR
57 | Camera Error
58 |
59 | Save
60 | Exempt Apps
61 | Search Apps
62 | Exit
63 | Loading…
64 | Exit without saving exempt app list?
65 | Save Server
66 | Share Link
67 | About
68 |
69 |
70 | About
71 | Credits
72 |
73 |
74 | Version
75 | Developed by
76 |
77 |
78 | Dreamacro/clash
79 | eycorsican/go-tun2socks
80 | bingoogolapple/BGAQRCode-Android
81 |
82 | About
83 |
84 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
19 |
20 |
29 |
30 |
35 |
36 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
9 |
12 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.6.3'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | gradle.projectsEvaluated {
24 | tasks.withType(JavaCompile) {
25 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
26 | }
27 | }
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | android.bundle.enableUncompressedNativeLibs=false
12 | org.gradle.jvmargs=-Xmx1536m
13 | # When configured, Gradle will run in incubating parallel mode.
14 | # This option should only be used with decoupled projects. More details, visit
15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
16 | # org.gradle.parallel=true
17 |
18 |
19 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasiscifr/igniter/37eb3a3463aec9c03da01ed4dedce97149360b78/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=`expr $i + 1`
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=`save "$@"`
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 | @rem Execute Gradle
88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
89 |
90 | :end
91 | @rem End local scope for the variables with windows NT shell
92 | if "%ERRORLEVEL%"=="0" goto mainEnd
93 |
94 | :fail
95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
96 | rem the _cmd.exe /c_ return code!
97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
98 | exit /b 1
99 |
100 | :mainEnd
101 | if "%OS%"=="Windows_NT" endlocal
102 |
103 | :omega
104 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------