├── .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 │ │ ├── TrojanURLParseResult.java │ │ ├── common │ │ ├── app │ │ │ ├── BaseAppCompatActivity.java │ │ │ └── BaseFragment.java │ │ ├── constants │ │ │ ├── ConfigFileConstants.java │ │ │ └── Constants.java │ │ ├── dialog │ │ │ └── LoadingDialog.java │ │ ├── mvp │ │ │ ├── BasePresenter.java │ │ │ └── BaseView.java │ │ ├── os │ │ │ ├── IThreads.java │ │ │ ├── PreferencesProvider.java │ │ │ ├── Task.java │ │ │ └── Threads.java │ │ ├── sp │ │ │ └── CommonSP.java │ │ └── utils │ │ │ ├── AnimationUtils.java │ │ │ ├── DecodeUtils.java │ │ │ ├── DisplayUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── 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 │ │ ├── QRCodeDecoder.java │ │ ├── ScanQRCodeActivity.java │ │ └── ScanQRCodeFragment.java │ │ ├── servers │ │ ├── ItemVerticalMoveCallback.java │ │ ├── SubscribeSettingDialog.java │ │ ├── activity │ │ │ └── ServerListActivity.java │ │ ├── contract │ │ │ └── ServerListContract.java │ │ ├── data │ │ │ ├── ServerListDataManager.java │ │ │ ├── ServerListDataSource.java │ │ │ └── TrojanConfigWrapper.java │ │ ├── fragment │ │ │ ├── ServerListAdapter.java │ │ │ └── ServerListFragment.java │ │ └── presenter │ │ │ └── ServerListPresenter.java │ │ ├── settings │ │ ├── InputEntryView.java │ │ ├── activity │ │ │ └── SettingsActivity.java │ │ ├── contract │ │ │ └── SettingsContract.java │ │ ├── data │ │ │ ├── ISettingsDataManager.java │ │ │ └── SettingsDataManager.java │ │ ├── fragment │ │ │ └── SettingsFragment.java │ │ └── presenter │ │ │ └── SettingsPresenter.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 │ ├── icon_add.png │ ├── icon_remove.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_main_720.xml │ ├── activity_scan_qrcode.xml │ ├── activity_server_list.xml │ ├── activity_settings.xml │ ├── dialog_loading.xml │ ├── dialog_subscribe_setting.xml │ ├── fragment_exempt_app.xml │ ├── fragment_scan_qr_code.xml │ ├── fragment_server_list.xml │ ├── fragment_settings.xml │ ├── item_app_info.xml │ └── item_server.xml │ ├── menu │ ├── menu_exempt_app.xml │ ├── menu_main.xml │ ├── menu_server_list.xml │ └── menu_settings.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-zh-rCN │ └── strings.xml │ ├── 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 ├── pull_golibs.py └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/code 5 | docker: 6 | - image: circleci/android:api-30 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.28/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 | [![CircleCI](https://circleci.com/gh/trojan-gfw/igniter/tree/master.svg?style=svg)](https://circleci.com/gh/trojan-gfw/igniter/tree/master) 4 | 5 | 6 | A trojan client for Android. 7 | 8 | Get it on Google Play 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 | 18 | 19 | 20 | #### P.S 21 | 22 | - Igniter has migrated from [bingoogolapple/BGAQRCode-Android](https://github.com/bingoogolapple/BGAQRCode-Android) to [ML Kit](https://developers.google.com/ml-kit/vision/barcode-scanning) for QR code scanning. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | ext.versionMajor = 0 4 | ext.versionMinor = 11 5 | ext.versionPatch = 0 6 | ext.versionClassifier = "beta" // or null 7 | ext.isSnapshot = true // set to false when publishing new releases 8 | ext.minimumSdkVersion = 23 9 | ext.targetSdkVersion = 31 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 "23.1.7779620" 29 | compileSdkVersion targetSdkVersion 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 true 59 | shrinkResources true 60 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 61 | 'proguard-rules.pro' 62 | } 63 | } 64 | externalNativeBuild { 65 | cmake { 66 | path file('src/main/cpp/CMakeLists.txt') 67 | } 68 | } 69 | compileOptions { 70 | // Flag to enable support for the new language APIs 71 | coreLibraryDesugaringEnabled true 72 | } 73 | packagingOptions { 74 | jniLibs { 75 | // During installation, the installer decompresses the libraries, and the linker loads 76 | // the decompressed libraries at runtime; in this case, the APK would be smaller, but 77 | // installation time might be slightly longer. 78 | // We optimize for size to make users happy 79 | useLegacyPackaging = true 80 | keepDebugSymbols += ['*/armeabi-v7a/libgojni.so', '*/arm64-v8a/libgojni.so', '*/x86/libgojni.so', '*/x86_64/libgojni.so'] 81 | } 82 | } 83 | splits { 84 | abi { 85 | enable true 86 | reset() 87 | include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" 88 | universalApk true 89 | } 90 | } 91 | } 92 | 93 | repositories { 94 | flatDir { 95 | dirs 'src/libs' 96 | } 97 | } 98 | 99 | dependencies { 100 | implementation 'com.github.stealthcopter:AndroidNetworkTools:0.4.5.3' 101 | implementation 'com.google.android.material:material:1.6.0-alpha01' 102 | implementation 'androidx.activity:activity:1.4.0' 103 | implementation 'androidx.fragment:fragment:1.4.0' 104 | implementation 'androidx.appcompat:appcompat:1.4.0' 105 | implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01' 106 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2' 107 | implementation 'androidx.core:core:1.8.0-alpha02' 108 | implementation 'androidx.preference:preference:1.2.0-rc01' 109 | implementation 'com.google.code.gson:gson:2.8.9' 110 | // CameraX core library using camera2 implementation 111 | implementation 'androidx.camera:camera-camera2:1.1.0-alpha12' 112 | // CameraX Lifecycle Library 113 | implementation 'androidx.camera:camera-lifecycle:1.1.0-alpha12' 114 | // CameraX View class 115 | implementation 'androidx.camera:camera-view:1.0.0-alpha32' 116 | implementation 'com.google.mlkit:barcode-scanning:17.0.1' 117 | 118 | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' 119 | testImplementation 'junit:junit:4.13.1' 120 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 121 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 122 | 123 | api(name: 'golibs', ext: 'aar') 124 | } 125 | -------------------------------------------------------------------------------- /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 | 23 | # for GSON 24 | -keep class io.github.trojan_gfw.igniter.TrojanURLParseResult { *; } 25 | -keep class io.github.trojan_gfw.igniter.TrojanConfig { *; } 26 | -------------------------------------------------------------------------------- /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 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 30 | 35 | 36 | 40 | 44 | 48 | 52 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /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 | String getProxyHost(); 11 | long getProxyPort(); 12 | oneway void registerCallback(in ITrojanServiceCallback callback); 13 | oneway void unregisterCallback(in ITrojanServiceCallback callback); 14 | } 15 | -------------------------------------------------------------------------------- /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/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/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 | public static void ShowConfig(String clashConfigPath) { 14 | File file = new File(clashConfigPath); 15 | 16 | try { 17 | try (FileInputStream fis = new FileInputStream(file)) { 18 | StringBuilder sb = new StringBuilder(); 19 | byte[] content = new byte[(int) file.length()]; 20 | fis.read(content); 21 | sb.append("\r\n"); 22 | sb.append(new String(content)); 23 | LogHelper.v(TAG, sb.toString()); 24 | } 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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 | import android.util.Log; 6 | 7 | import java.io.File; 8 | 9 | import io.github.trojan_gfw.igniter.common.constants.ConfigFileConstants; 10 | 11 | public class Globals { 12 | 13 | private static String exportDir; 14 | private static String cacheDir; 15 | private static String filesDir; 16 | private static TrojanConfig trojanConfigInstance; 17 | 18 | public static void Init(Context ctx) { 19 | cacheDir = ctx.getCacheDir().getAbsolutePath(); 20 | filesDir = ctx.getFilesDir().getAbsolutePath(); 21 | trojanConfigInstance = new TrojanConfig(); 22 | exportDir = exportDir(ctx, filesDir); 23 | } 24 | 25 | private static String exportDir(Context ctx, String defaultDir) { 26 | try { 27 | if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 28 | // external storage not ready 29 | return defaultDir; 30 | } 31 | //external storage is ready 32 | File externalFilesDir = ctx.getExternalFilesDir(ConfigFileConstants.CONFIGS); 33 | if (externalFilesDir == null) { 34 | return defaultDir; 35 | } 36 | return externalFilesDir.getAbsolutePath(); 37 | } catch (Exception e) { 38 | Log.e("globals", "get export dir error", e); 39 | } 40 | return defaultDir; 41 | } 42 | 43 | public static String getCaCertPath() { 44 | return PathHelper.combine(cacheDir, "cacert.pem"); 45 | } 46 | 47 | public static String getCountryMmdbPath() { 48 | return PathHelper.combine(filesDir, "Country.mmdb"); 49 | } 50 | 51 | public static String getClashConfigPath() { 52 | return PathHelper.combine(filesDir, "config.yaml"); 53 | } 54 | 55 | public static String getTrojanConfigPath() { 56 | return PathHelper.combine(filesDir, "config.json"); 57 | } 58 | 59 | public static String getTrojanConfigListPath() { 60 | return PathHelper.combine(filesDir, "config_list.json"); 61 | } 62 | 63 | public static String getIgniterExportPath() { 64 | return PathHelper.combine(exportDir, "config_list.txt"); 65 | } 66 | 67 | public static String getPreferencesFilePath() { 68 | return PathHelper.combine(filesDir, "preferences.txt"); 69 | } 70 | 71 | public static String getBlockedAppListPath() { 72 | return PathHelper.combine(filesDir, "exempted_app_list.txt"); 73 | } 74 | 75 | public static String getAllowedAppListPath() { 76 | return PathHelper.combine(filesDir, "allow_app_list.txt"); 77 | } 78 | 79 | public static void setTrojanConfigInstance(TrojanConfig config) { 80 | trojanConfigInstance = config; 81 | } 82 | 83 | public static TrojanConfig getTrojanConfigInstance() { 84 | return trojanConfigInstance; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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 | * 96 | * @see #endUpdates() 97 | */ 98 | protected void startUpdates() { 99 | _ignore = true; 100 | } 101 | 102 | /** 103 | * Call this method when you finished to update the text view in order to restart to listen to it. 104 | * 105 | * @see #startUpdates() 106 | */ 107 | protected void endUpdates() { 108 | _ignore = false; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/TrojanURLHelper.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.net.URI; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class TrojanURLHelper { 11 | public static String GenerateTrojanURL(TrojanConfig trojanConfig) { 12 | 13 | URI trojanUri; 14 | try { 15 | String serverRemark = trojanConfig.getRemoteServerRemark(); 16 | trojanUri = new URI("trojan", 17 | trojanConfig.getPassword(), 18 | trojanConfig.getRemoteAddr(), 19 | trojanConfig.getRemotePort(), 20 | null, null, 21 | (serverRemark == null || serverRemark.length() <= 0) ? null : serverRemark); 22 | } catch (Exception e) { 23 | e.printStackTrace(); 24 | return null; 25 | } 26 | 27 | return trojanUri.toASCIIString(); 28 | } 29 | 30 | public static TrojanURLParseResult ParseTrojanURL(String trojanURLStr) { 31 | URI trojanUri; 32 | try { 33 | trojanUri = new URI(trojanURLStr); 34 | } catch (java.net.URISyntaxException e) { 35 | e.printStackTrace(); 36 | return null; 37 | } 38 | String scheme = trojanUri.getScheme(); 39 | if (scheme == null) { 40 | return null; 41 | } 42 | if (!scheme.equals("trojan")) 43 | return null; 44 | String host = trojanUri.getHost(); 45 | int port = trojanUri.getPort(); 46 | String userInfo = trojanUri.getUserInfo(); 47 | String serverRemark = trojanUri.getFragment(); 48 | serverRemark = TrojanHelper.RemoveAllEmoji(serverRemark); 49 | 50 | TrojanURLParseResult retConfig = new TrojanURLParseResult(); 51 | retConfig.host = host; 52 | retConfig.port = port; 53 | retConfig.password = userInfo; 54 | retConfig.serverRemark = serverRemark; 55 | return retConfig; 56 | } 57 | 58 | public static List ParseMultipleTrojanURL(String inputStr) { 59 | ArrayList ret = new ArrayList(5); 60 | String[] trojanURLLines = inputStr.split("\\R+"); 61 | 62 | for (String trojanURLLine : trojanURLLines) { 63 | TrojanURLParseResult parseResult = TrojanURLHelper.ParseTrojanURL(trojanURLLine); 64 | if (parseResult != null) { 65 | ret.add(parseResult); 66 | } 67 | } 68 | return ret; 69 | } 70 | 71 | public static List ParseTrojanConfigsFromContent(String content) { 72 | ArrayList ret = new ArrayList(5); 73 | List parseResults = ParseMultipleTrojanURL(content); 74 | for (TrojanURLParseResult singleParseResult : parseResults) { 75 | TrojanConfig newConfig = CombineTrojanURLParseResultToTrojanConfig(singleParseResult, Globals.getTrojanConfigInstance()); 76 | ret.add(newConfig); 77 | } 78 | return ret; 79 | } 80 | 81 | /** 82 | * Merge Trojan URL parse result and Trojan config instance 83 | * 84 | * @param srcParseResult parsed Trojan URL 85 | * @param srcConfig the source Trojan config instance 86 | * @return A deep clone of the source Trojan config instance 87 | */ 88 | public static TrojanConfig CombineTrojanURLParseResultToTrojanConfig(TrojanURLParseResult srcParseResult, 89 | TrojanConfig srcConfig) { 90 | if (srcConfig == null || srcParseResult == null) { 91 | return null; 92 | } 93 | Gson gson = new Gson(); 94 | 95 | TrojanConfig dstConfig = gson.fromJson(gson.toJson(srcConfig), TrojanConfig.class); 96 | TrojanURLParseResult dstParseResult = gson.fromJson(gson.toJson(srcParseResult), TrojanURLParseResult.class); 97 | dstConfig.setRemoteAddr(dstParseResult.host); 98 | dstConfig.setRemotePort(dstParseResult.port); 99 | dstConfig.setPassword(dstParseResult.password); 100 | if (dstParseResult.serverRemark != null && dstParseResult.serverRemark.length() > 0) 101 | dstConfig.setRemoteServerRemark(dstParseResult.serverRemark); 102 | return dstConfig; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/TrojanURLParseResult.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter; 2 | 3 | public class TrojanURLParseResult { 4 | public String host; 5 | public int port; 6 | public String password; 7 | public String serverRemark; 8 | } 9 | -------------------------------------------------------------------------------- /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 onAttach(@NonNull Context context) { 19 | super.onAttach(context); 20 | mContext = context; 21 | } 22 | 23 | @Override 24 | public void onDetach() { 25 | super.onDetach(); 26 | mContext = null; 27 | } 28 | 29 | @Override 30 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 31 | super.onViewCreated(view, savedInstanceState); 32 | mRootView = view; 33 | } 34 | 35 | protected T findViewById(@IdRes int id) { 36 | return mRootView.findViewById(id); 37 | } 38 | 39 | protected void runOnUiThread(Runnable runnable) { 40 | FragmentActivity activity = getActivity(); 41 | if (activity != null) { 42 | activity.runOnUiThread(runnable); 43 | } 44 | } 45 | 46 | protected void finishActivity() { 47 | FragmentActivity activity = getActivity(); 48 | if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) { 49 | activity.finish(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/common/constants/ConfigFileConstants.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.common.constants; 2 | 3 | /** 4 | * @author Chris 5 | * @version 1.1.0 6 | * @date 2020/9/27 7 | */ 8 | public class ConfigFileConstants { 9 | public static final String CONFIGS = "configs"; 10 | public static final String EMPTY_STRING = ""; 11 | public static final String REMARKS = "remarks"; 12 | public static final String SERVER = "server"; 13 | public static final String SERVER_PORT = "server_port"; 14 | public static final String SNI = "sni"; 15 | public static final String PASSWORD = "password"; 16 | public static final String VERIFY = "verify"; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/common/constants/Constants.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.common.constants; 2 | 3 | import io.github.trojan_gfw.igniter.BuildConfig; 4 | 5 | public abstract class Constants { 6 | public static final String PREFERENCE_AUTHORITY = BuildConfig.APPLICATION_ID; 7 | public static final String PREFERENCE_PATH = "preferences"; 8 | public static final String PREFERENCE_URI = "content://" + PREFERENCE_AUTHORITY + "/" + PREFERENCE_PATH; 9 | public static final String PREFERENCE_KEY_ENABLE_CLASH = "enable_clash"; 10 | public static final String PREFERENCE_KEY_ALLOW_LAN = "allow_lan"; 11 | public static final String PREFERENCE_KEY_FIRST_START = "first_start"; 12 | public static final String PREFERENCE_KEY_PROXY_IN_ALLOW_MODE = "proxy_allow_mode"; 13 | public static final String PREFERENCE_KEY_EXTRA_DNS = "extra_dns"; 14 | public static final String PREFERENCE_KEY_FIXED_PORT = "fixed_port"; 15 | } 16 | -------------------------------------------------------------------------------- /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.PorterDuffColorFilter; 8 | import android.graphics.drawable.ColorDrawable; 9 | import android.view.Window; 10 | import android.widget.TextView; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.core.content.ContextCompat; 14 | import androidx.core.widget.ContentLoadingProgressBar; 15 | 16 | import io.github.trojan_gfw.igniter.R; 17 | 18 | public class LoadingDialog extends Dialog { 19 | private TextView mMsgTv; 20 | 21 | public LoadingDialog(@NonNull Context context) { 22 | super(context); 23 | init(context); 24 | } 25 | 26 | private void init(Context context) { 27 | Window window = getWindow(); 28 | if (window != null) { 29 | window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 30 | } 31 | setContentView(R.layout.dialog_loading); 32 | ContentLoadingProgressBar pb = findViewById(R.id.dialogLoadingPb); 33 | pb.getIndeterminateDrawable().setColorFilter( 34 | new PorterDuffColorFilter(ContextCompat.getColor(context, R.color.colorPrimary), 35 | PorterDuff.Mode.MULTIPLY)); 36 | mMsgTv = findViewById(R.id.dialogLoadingMsgTv); 37 | } 38 | 39 | public void setMsg(String msg) { 40 | mMsgTv.setText(msg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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/sp/CommonSP.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.common.sp; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | public class CommonSP { 7 | private static final String SP_NAME = "common_sp"; 8 | private static final String KEY_SERVER_SUBSCRIBE_URL = "server_sub_url"; 9 | private static final String KEY_EXTRA_DNS = "EXTRA_DNS"; 10 | private static Context sContext; 11 | 12 | public static void init(Context context) { 13 | sContext = context; 14 | } 15 | 16 | private static SharedPreferences sp() { 17 | return sContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); 18 | } 19 | 20 | private static SharedPreferences.Editor edit() { 21 | return sp().edit(); 22 | } 23 | 24 | public static String getServerSubscribeUrl(String defaultVal) { 25 | return sp().getString(KEY_SERVER_SUBSCRIBE_URL, defaultVal); 26 | } 27 | 28 | public static void setServerSubscribeUrl(String url) { 29 | edit().putString(KEY_SERVER_SUBSCRIBE_URL, url).apply(); 30 | } 31 | 32 | public static String getExtraDNSJSONArrayString(String defaultValue) { 33 | return sp().getString(KEY_EXTRA_DNS, defaultValue); 34 | } 35 | 36 | public static void setExtraDNSJSONArrayString(String jsonArrayString) { 37 | edit().putString(KEY_EXTRA_DNS, jsonArrayString).apply(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/common/utils/AnimationUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.common.utils; 2 | 3 | import android.view.View; 4 | import android.view.animation.AccelerateDecelerateInterpolator; 5 | import android.view.animation.Animation; 6 | import android.view.animation.AnimationSet; 7 | import android.view.animation.RotateAnimation; 8 | 9 | public class AnimationUtils { 10 | /** 11 | * Perform an sway animation on view. The view would start swaying from 0° to (maxSwayPeriodDuration / 2°) clockwise. 12 | * Then it sways to the axisymmetric position (in respect to the vertical center axis of the view) and sways back. 13 | * The view will keep swaying till the nextDegree reaches 0. 14 | * Each time the sway amplitude is decreased by {@param swayDegreeDecrementStep}. 15 | * 16 | * @param view The view to be animated 17 | * @param maxSwayDegree Maximum sway amplitude. 18 | * @param maxSwayPeriodDuration The sway duration for maximum sway amplitude. 19 | * @param swayDegreeDecrementStep Step of sway amplitude decrement. Must be larger than 0. 20 | */ 21 | public static void sway(View view, float maxSwayDegree, long maxSwayPeriodDuration, float swayDegreeDecrementStep) { 22 | if (Float.compare(swayDegreeDecrementStep, 0f) <= 0) { 23 | throw new IllegalArgumentException("swayDegreeDecrementStep must be >= 0!"); 24 | } 25 | AnimationSet rotateSet = new AnimationSet(true); 26 | rotateSet.setInterpolator(new AccelerateDecelerateInterpolator()); 27 | float lastDegree = maxSwayDegree / 2f; 28 | Animation prepareAnim = new RotateAnimation(0f, lastDegree, Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f); 29 | prepareAnim.setDuration(maxSwayPeriodDuration >> 1); 30 | rotateSet.addAnimation(prepareAnim); 31 | long startOffSet = maxSwayPeriodDuration >> 1; 32 | boolean run = true; 33 | while (run) { 34 | float nextDegree; 35 | if (lastDegree > 0f) { 36 | nextDegree = -lastDegree; 37 | } else { 38 | nextDegree = -lastDegree - swayDegreeDecrementStep; 39 | if (Float.compare(nextDegree, 0f) <= 0) { 40 | run = false; 41 | } 42 | } 43 | double degreeDistance = Math.abs(lastDegree) + Math.abs(nextDegree); 44 | long duration = (long) ((degreeDistance / maxSwayDegree) * maxSwayPeriodDuration); 45 | Animation rotate = new RotateAnimation(lastDegree, nextDegree, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 46 | rotate.setDuration(duration); 47 | rotate.setStartOffset(startOffSet); 48 | startOffSet += duration; 49 | lastDegree = nextDegree; 50 | rotateSet.addAnimation(rotate); 51 | } 52 | view.startAnimation(rotateSet); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/common/utils/DecodeUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.common.utils; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Base64; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | 8 | public class DecodeUtils { 9 | 10 | public static String decodeBase64(String rawStr) { 11 | if (TextUtils.isEmpty(rawStr)) { 12 | return rawStr; 13 | } 14 | try { 15 | byte[] decode = Base64.decode(rawStr, Base64.DEFAULT); 16 | return new String(decode, StandardCharsets.UTF_8); 17 | } catch (Exception e) { 18 | e.printStackTrace(); 19 | return null; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/common/utils/DisplayUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.common.utils; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.DisplayMetrics; 6 | 7 | public class DisplayUtils { 8 | public static int getScreenWidth() { 9 | Resources resources = Resources.getSystem(); 10 | DisplayMetrics dm = resources.getDisplayMetrics(); 11 | return dm.widthPixels; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/common/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.common.utils; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | 8 | public class FileUtils { 9 | 10 | public static boolean copy(File src, File dest) { 11 | try (FileOutputStream fos = new FileOutputStream(dest); 12 | FileInputStream fis = new FileInputStream(src)) { 13 | byte[] buff = new byte[4096]; 14 | int readBytes; 15 | while ((readBytes = fis.read(buff)) != -1) { 16 | fos.write(buff, 0, readBytes); 17 | } 18 | } catch (IOException e) { 19 | e.printStackTrace(); 20 | } 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | return getPreference(resolver, uri, key, defaultValue, ((cursor, columnIndex, type, defValue) -> { 14 | switch (type) { 15 | case Cursor.FIELD_TYPE_STRING: 16 | return Boolean.parseBoolean(cursor.getString(columnIndex)); 17 | case Cursor.FIELD_TYPE_INTEGER: 18 | return cursor.getInt(columnIndex) == 1; 19 | default: 20 | return defValue; 21 | } 22 | })); 23 | } 24 | 25 | public static void putBooleanPreference(ContentResolver resolver, Uri uri, String key, boolean value) { 26 | putPreference(resolver, uri, key, value, (v, contentValues) -> contentValues.put(key, v)); 27 | } 28 | 29 | public static String getStringPreference(ContentResolver resolver, Uri uri, String key, String defVal) { 30 | return getPreference(resolver, uri, key, defVal, ((cursor, columnIndex, type, defValue) -> { 31 | if (Cursor.FIELD_TYPE_STRING == type) { 32 | return cursor.getString(columnIndex); 33 | } 34 | return defVal; 35 | })); 36 | } 37 | 38 | public static void putStringPreference(ContentResolver resolver, Uri uri, String key, String value) { 39 | putPreference(resolver, uri, key, value, (v, contentValues) -> contentValues.put(key, v)); 40 | } 41 | 42 | public static void putIntPreference(ContentResolver resolver, Uri uri, String key, int value) { 43 | putPreference(resolver, uri, key, value, (v, contentValues) -> contentValues.put(key, v)); 44 | } 45 | 46 | public static int getIntPreference(ContentResolver resolver, Uri uri, String key, int defVal) { 47 | return getPreference(resolver, uri, key, defVal, ((cursor, columnIndex, type, defValue) -> { 48 | if (Cursor.FIELD_TYPE_INTEGER == type) { 49 | return cursor.getInt(columnIndex); 50 | } 51 | return defVal; 52 | })); 53 | } 54 | 55 | private static void putPreference(ContentResolver resolver, Uri uri, String key, T value, 56 | ContentValuePutter putter) { 57 | ContentValues contentValues = new ContentValues(1); 58 | putter.onPutValue(value, contentValues); 59 | resolver.update(uri, contentValues, null, null); 60 | } 61 | 62 | private static T getPreference(ContentResolver resolver, Uri uri, String key, T defVal, 63 | CursorReader reader) { 64 | try (Cursor query = ContentResolverCompat.query(resolver, uri, new String[]{key}, null, 65 | null, null, null)) { 66 | if (query == null) return defVal; 67 | if (query.moveToFirst()) { 68 | int columnIndex = query.getColumnIndex(key); 69 | if (columnIndex >= 0) { 70 | int type = query.getType(columnIndex); 71 | return reader.onReadValue(query, columnIndex, type, defVal); 72 | } 73 | } 74 | } 75 | return defVal; 76 | } 77 | 78 | interface ContentValuePutter { 79 | void onPutValue(T value, ContentValues contentValues); 80 | } 81 | 82 | interface CursorReader { 83 | T onReadValue(Cursor cursor, int columnIndex, int type, T defValue); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /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 Snackbar makeTextShort(View view, @StringRes int id) { 12 | return Snackbar.make(view, id, Snackbar.LENGTH_SHORT); 13 | } 14 | 15 | public static void showTextShort(View view, @StringRes int id) { 16 | Snackbar.make(view, id, Snackbar.LENGTH_SHORT).show(); 17 | } 18 | 19 | public static void showTextShort(View view, @StringRes int id, @StringRes int actionId, View.OnClickListener listener) { 20 | Snackbar.make(view, id, Snackbar.LENGTH_SHORT).setAction(actionId, listener).show(); 21 | } 22 | 23 | public static void showTextShort(View view, String text, String actionText, View.OnClickListener listener) { 24 | Snackbar.make(view, text, Snackbar.LENGTH_SHORT).setAction(actionText, listener).show(); 25 | } 26 | 27 | public static void showTextShort(View view, String text) { 28 | Snackbar.make(view, text, Snackbar.LENGTH_SHORT).show(); 29 | } 30 | 31 | public static void showTextLong(View view, @StringRes int id) { 32 | Snackbar.make(view, id, Snackbar.LENGTH_LONG).show(); 33 | } 34 | 35 | public static void showTextLong(View view, @StringRes int id, @StringRes int actionId, View.OnClickListener listener) { 36 | Snackbar.make(view, id, Snackbar.LENGTH_LONG).setAction(actionId, listener).show(); 37 | } 38 | 39 | public static void showTextLong(View view, String text) { 40 | Snackbar.make(view, text, Snackbar.LENGTH_LONG).show(); 41 | } 42 | 43 | public static void showTextLong(View view, String text, String actionText, View.OnClickListener listener) { 44 | Snackbar.make(view, text, Snackbar.LENGTH_LONG).setAction(actionText, listener).show(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/connection/TestConnection.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.connection; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.net.Proxy; 5 | import java.net.URL; 6 | import java.net.URLConnection; 7 | 8 | public class TestConnection { 9 | private static final int DEFAULT_TIMEOUT = 10 * 1000; // 10 seconds 10 | private final String mProxyHost; 11 | private final long mProxyPort; 12 | private final OnResultListener mOnResultListener; 13 | 14 | public TestConnection(String proxyHost, long proxyPort, OnResultListener onResultListener) { 15 | mProxyHost = proxyHost; 16 | mProxyPort = proxyPort; 17 | mOnResultListener = onResultListener; 18 | } 19 | 20 | public void testLatency(String testUrl) { 21 | try { 22 | long startTime = System.currentTimeMillis(); 23 | InetSocketAddress proxyAddress = new InetSocketAddress(mProxyHost, (int) mProxyPort); 24 | Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress); 25 | URLConnection connection = new URL(testUrl).openConnection(proxy); 26 | connection.setConnectTimeout(DEFAULT_TIMEOUT); 27 | connection.setReadTimeout(DEFAULT_TIMEOUT); 28 | connection.connect(); 29 | mOnResultListener.onResult(testUrl, true, System.currentTimeMillis() - startTime, ""); 30 | } catch (Exception e) { 31 | mOnResultListener.onResult(testUrl, false, 0L, e.getMessage()); 32 | } 33 | } 34 | 35 | public interface OnResultListener { 36 | void onResult(String testUrl, boolean connected, long delay, String error); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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(this, fragment, new ExemptAppDataManager(getApplicationContext(), 36 | Globals.getBlockedAppListPath(), Globals.getAllowedAppListPath())); 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 loadBlockAppListConfig(); 26 | 27 | void loadAllowAppListConfig(); 28 | 29 | void exit(); 30 | } 31 | 32 | interface View extends BaseView { 33 | 34 | @UiThread 35 | void showLoading(); 36 | 37 | @UiThread 38 | void dismissLoading(); 39 | 40 | @UiThread 41 | void showSaveSuccess(); 42 | 43 | @UiThread 44 | void showExitConfirm(); 45 | 46 | @UiThread 47 | void showBlockAppList(List packageNames); 48 | 49 | @UiThread 50 | void showAllowAppList(List packagesNames); 51 | 52 | @AnyThread 53 | void exit(boolean configurationChanged); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /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 | import java.util.Optional; 6 | 7 | public class AppInfo implements Cloneable, Comparable { 8 | private String appName; 9 | private String appNameInLowercase; 10 | private Drawable icon; 11 | private String packageName; 12 | private boolean exempt; 13 | 14 | public String getAppName() { 15 | return appName; 16 | } 17 | 18 | public String getAppNameInLowercase() { 19 | if (appNameInLowercase == null) { 20 | // lazy loading. 21 | appNameInLowercase = appName.toLowerCase(); 22 | } 23 | return appNameInLowercase; 24 | } 25 | 26 | public void setAppName(String appName) { 27 | this.appName = appName; 28 | } 29 | 30 | public Drawable getIcon() { 31 | return icon; 32 | } 33 | 34 | public void setIcon(Drawable icon) { 35 | this.icon = icon; 36 | } 37 | 38 | public String getPackageName() { 39 | return packageName; 40 | } 41 | 42 | public void setPackageName(String packageName) { 43 | this.packageName = packageName; 44 | } 45 | 46 | public boolean isExempt() { 47 | return exempt; 48 | } 49 | 50 | public void setExempt(boolean exempt) { 51 | this.exempt = exempt; 52 | } 53 | 54 | @Override 55 | public int compareTo(AppInfo o) { 56 | if (exempt ^ o.exempt) { 57 | return exempt ? -1 : 1; 58 | } 59 | if (appName == null) { 60 | return "".compareTo(o.appName); 61 | } 62 | return appName.compareTo(o.appName); 63 | } 64 | 65 | @Override 66 | protected Object clone() throws CloneNotSupportedException { 67 | AppInfo appInfo = (AppInfo) super.clone(); 68 | appInfo.appName = appName; 69 | appInfo.icon = icon; 70 | appInfo.packageName = packageName; 71 | appInfo.exempt = exempt; 72 | return appInfo; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /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 | import androidx.annotation.Nullable; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.BufferedWriter; 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.io.InputStreamReader; 19 | import java.io.OutputStreamWriter; 20 | import java.util.ArrayList; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Set; 24 | 25 | /** 26 | * Implementation of {@link ExemptAppDataSource}. This class reads and writes exempted app list in a 27 | * file. The exempted app package names will be written line by line in the file. 28 | *
29 | * Example: 30 | *
31 | * com.google.playstore 32 | *
33 | * io.github.trojan_gfw.igniter 34 | *
35 | * com.android.something 36 | */ 37 | public class ExemptAppDataManager implements ExemptAppDataSource { 38 | private final PackageManager mPackageManager; 39 | private final String mBlockAppListFilePath; 40 | private final String mAllowAppListFilePath; 41 | 42 | public ExemptAppDataManager(Context context, String blockAppListFilePath, 43 | String allowAppListFilePath) { 44 | super(); 45 | mPackageManager = context.getPackageManager(); 46 | mBlockAppListFilePath = blockAppListFilePath; 47 | mAllowAppListFilePath = allowAppListFilePath; 48 | } 49 | 50 | @Override 51 | public void saveAllowAppInfoSet(@Nullable Set allowAppPackageNames) { 52 | saveAppPackageNameSet(allowAppPackageNames, mAllowAppListFilePath); 53 | } 54 | 55 | @Override 56 | public void saveBlockAppInfoSet(@Nullable Set blockAppPackageNames) { 57 | saveAppPackageNameSet(blockAppPackageNames, mBlockAppListFilePath); 58 | } 59 | 60 | private void saveAppPackageNameSet(@Nullable Set packageNameSet, String filePath) { 61 | File file = new File(filePath); 62 | if (file.exists()) { 63 | file.delete(); 64 | } 65 | if (packageNameSet == null || packageNameSet.isEmpty()) { 66 | return; 67 | } 68 | File dir = file.getParentFile(); 69 | if (!dir.exists()) { 70 | dir.mkdirs(); 71 | } 72 | try (FileOutputStream fos = new FileOutputStream(file); 73 | OutputStreamWriter osw = new OutputStreamWriter(fos); 74 | BufferedWriter bw = new BufferedWriter(osw)) { 75 | for (String name : packageNameSet) { 76 | bw.write(name); 77 | bw.write('\n'); 78 | } 79 | bw.flush(); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | 85 | @NonNull 86 | private Set readExemptAppListConfig(String filePath) { 87 | File file = new File(filePath); 88 | Set exemptAppSet = new HashSet<>(); 89 | if (!file.exists()) { 90 | return exemptAppSet; 91 | } 92 | try (FileInputStream fis = new FileInputStream(file); 93 | InputStreamReader isr = new InputStreamReader(fis); 94 | BufferedReader reader = new BufferedReader(isr)) { 95 | String tmp = reader.readLine(); 96 | while (!TextUtils.isEmpty(tmp)) { 97 | exemptAppSet.add(tmp); 98 | tmp = reader.readLine(); 99 | } 100 | } catch (IOException e) { 101 | e.printStackTrace(); 102 | } 103 | return exemptAppSet; 104 | } 105 | 106 | @Override 107 | public Set loadAllowAppPackageNameSet() { 108 | return loadAppPackageNameSet(mAllowAppListFilePath); 109 | } 110 | 111 | @Override 112 | public Set loadBlockAppPackageNameSet() { 113 | return loadAppPackageNameSet(mBlockAppListFilePath); 114 | } 115 | 116 | private Set loadAppPackageNameSet(String filePath) { 117 | Set exemptAppPackageNames = readExemptAppListConfig(filePath); 118 | // filter uninstalled apps 119 | List applicationInfoList = queryCurrentInstalledApps(); 120 | Set installedAppPackageNames = new HashSet<>(); 121 | for (ApplicationInfo applicationInfo : applicationInfoList) { 122 | installedAppPackageNames.add(applicationInfo.packageName); 123 | } 124 | Set ret = new HashSet<>(); 125 | for (String packageName : exemptAppPackageNames) { 126 | if (installedAppPackageNames.contains(packageName)) { 127 | ret.add(packageName); 128 | } 129 | } 130 | return ret; 131 | } 132 | 133 | private List queryCurrentInstalledApps() { 134 | int flags = 0; 135 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 136 | flags |= PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS; 137 | } else { // These flags are deprecated since Nougat. 138 | flags |= PackageManager.GET_UNINSTALLED_PACKAGES; 139 | } 140 | return mPackageManager.getInstalledApplications(flags); 141 | } 142 | 143 | @Override 144 | public List getAllAppInfoList() { 145 | List applicationInfoList = queryCurrentInstalledApps(); 146 | List appInfoList = new ArrayList<>(applicationInfoList.size()); 147 | for (ApplicationInfo applicationInfo : applicationInfoList) { 148 | AppInfo appInfo = new AppInfo(); 149 | appInfo.setAppName(mPackageManager.getApplicationLabel(applicationInfo).toString()); 150 | appInfo.setPackageName(applicationInfo.packageName); 151 | appInfo.setIcon(mPackageManager.getApplicationIcon(applicationInfo)); 152 | appInfoList.add(appInfo); 153 | } 154 | return appInfoList; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /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.Nullable; 4 | import androidx.annotation.WorkerThread; 5 | 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | public interface ExemptAppDataSource { 10 | /** 11 | * Load exempt applications' package names. 12 | * 13 | * @return exempt applications' package names.. 14 | */ 15 | @WorkerThread 16 | Set loadBlockAppPackageNameSet(); 17 | 18 | /** 19 | * Load package names of applications to whom the proxy is applied. 20 | */ 21 | @WorkerThread 22 | Set loadAllowAppPackageNameSet(); 23 | 24 | /** 25 | * Save exempt applications' package names. 26 | * 27 | * @param blockAppPackageNames exempt app package name set 28 | */ 29 | @WorkerThread 30 | void saveBlockAppInfoSet(@Nullable Set blockAppPackageNames); 31 | 32 | @WorkerThread 33 | void saveAllowAppInfoSet(@Nullable Set allowAppPackageNames); 34 | 35 | /** 36 | * Load all application info list, including exempt apps and non-exempt apps. 37 | * 38 | * @return all application info list 39 | */ 40 | @WorkerThread 41 | List getAllAppInfoList(); 42 | } 43 | -------------------------------------------------------------------------------- /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 | import io.github.trojan_gfw.igniter.common.sp.CommonSP; 7 | 8 | /** 9 | * Initializer that runs in Main Process (Default process). 10 | */ 11 | public class MainInitializer extends Initializer { 12 | 13 | @Override 14 | public void init(Context context) { 15 | Globals.Init(context); 16 | CommonSP.init(context); 17 | } 18 | 19 | @Override 20 | public boolean runsInWorkerThread() { 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | Globals.setTrojanConfigInstance(cacheConfig); 21 | } 22 | if (!Globals.getTrojanConfigInstance().isValidRunningConfig()) { 23 | LogHelper.e(TAG, "Invalid trojan config!"); 24 | } 25 | } 26 | 27 | @Override 28 | public boolean runsInWorkerThread() { 29 | return false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /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/QRCodeDecoder.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.qrcode; 2 | 3 | import android.graphics.Bitmap; 4 | import android.text.TextUtils; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.google.mlkit.vision.barcode.common.Barcode; 9 | import com.google.mlkit.vision.barcode.BarcodeScanner; 10 | import com.google.mlkit.vision.barcode.BarcodeScannerOptions; 11 | import com.google.mlkit.vision.barcode.BarcodeScanning; 12 | import com.google.mlkit.vision.common.InputImage; 13 | 14 | import java.util.Optional; 15 | 16 | public class QRCodeDecoder { 17 | private final BarcodeScanner mScanner; 18 | 19 | public QRCodeDecoder() { 20 | BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() 21 | .setBarcodeFormats(Barcode.FORMAT_QR_CODE).build(); 22 | mScanner = BarcodeScanning.getClient(options); 23 | } 24 | 25 | public void decode(@NonNull Bitmap bitmap, @NonNull OnQRCodeDecodeListener listener) { 26 | mScanner.process(InputImage.fromBitmap(bitmap, 0)).addOnSuccessListener(barcodes -> { 27 | Optional qrCode = barcodes.stream().filter(barcode -> { 28 | String rawValue = barcode.getRawValue(); 29 | return rawValue != null && !TextUtils.isEmpty(rawValue.trim()); 30 | }).findFirst(); 31 | if (qrCode.isPresent()) { 32 | listener.onSuccess(qrCode.get().getRawValue()); 33 | } else { 34 | listener.onNoQRCode(); 35 | } 36 | }).addOnCanceledListener(listener::onNoQRCode) 37 | .addOnFailureListener(listener::onError) 38 | .addOnCompleteListener(barcodes -> listener.onComplete()); 39 | } 40 | 41 | public void close() { 42 | mScanner.close(); 43 | } 44 | 45 | public interface OnQRCodeDecodeListener { 46 | void onSuccess(String qrCode); 47 | 48 | void onError(Exception e); 49 | 50 | void onNoQRCode(); 51 | 52 | void onComplete(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/qrcode/ScanQRCodeFragment.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.qrcode; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.media.Image; 8 | import android.os.Bundle; 9 | import android.text.TextUtils; 10 | import android.util.Size; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.Toast; 15 | 16 | import androidx.annotation.NonNull; 17 | import androidx.annotation.Nullable; 18 | import androidx.camera.core.CameraSelector; 19 | import androidx.camera.core.ImageAnalysis; 20 | import androidx.camera.core.ImageProxy; 21 | import androidx.camera.core.Preview; 22 | import androidx.camera.lifecycle.ProcessCameraProvider; 23 | import androidx.camera.view.PreviewView; 24 | import androidx.core.content.ContextCompat; 25 | 26 | import com.google.common.util.concurrent.ListenableFuture; 27 | import com.google.mlkit.vision.barcode.common.Barcode; 28 | import com.google.mlkit.vision.barcode.BarcodeScanner; 29 | import com.google.mlkit.vision.barcode.BarcodeScannerOptions; 30 | import com.google.mlkit.vision.barcode.BarcodeScanning; 31 | import com.google.mlkit.vision.common.InputImage; 32 | 33 | import java.util.concurrent.ExecutionException; 34 | import java.util.concurrent.Executor; 35 | import java.util.concurrent.ExecutorService; 36 | import java.util.concurrent.Executors; 37 | 38 | import io.github.trojan_gfw.igniter.LogHelper; 39 | import io.github.trojan_gfw.igniter.R; 40 | import io.github.trojan_gfw.igniter.common.app.BaseFragment; 41 | 42 | public class ScanQRCodeFragment extends BaseFragment implements ImageAnalysis.Analyzer { 43 | public static final String TAG = "ScanQRCodeFragment"; 44 | private static final int DEFAULT_IMAGE_ANALYSIS_WIDTH = 1080; 45 | private static final int DEFAULT_IMAGE_ANALYSIS_HEIGHT = 1920; 46 | private static final String KEY_SCAN_CONTENT = ScanQRCodeActivity.KEY_SCAN_CONTENT; 47 | private BarcodeScanner mScanner; 48 | private Executor mMainExecutor; 49 | private ExecutorService mCameraExecutor; 50 | private ImageAnalysis mImageAnalysis; 51 | 52 | public static ScanQRCodeFragment newInstance() { 53 | return new ScanQRCodeFragment(); 54 | } 55 | 56 | @Nullable 57 | @Override 58 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 59 | return inflater.inflate(R.layout.fragment_scan_qr_code, container, false); 60 | } 61 | 62 | @Override 63 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 64 | super.onViewCreated(view, savedInstanceState); 65 | prepareForScanning(); 66 | startCamera(); 67 | } 68 | 69 | private void prepareForScanning() { 70 | BarcodeScannerOptions options = new BarcodeScannerOptions.Builder() 71 | .setBarcodeFormats(Barcode.FORMAT_QR_CODE).build(); 72 | mScanner = BarcodeScanning.getClient(options); 73 | mMainExecutor = ContextCompat.getMainExecutor(mContext); 74 | mCameraExecutor = Executors.newSingleThreadExecutor(); 75 | } 76 | 77 | private ImageAnalysis getImageAnalysis() { 78 | if (mImageAnalysis == null) { 79 | mImageAnalysis = new ImageAnalysis.Builder() 80 | .setTargetResolution(new Size(DEFAULT_IMAGE_ANALYSIS_WIDTH, DEFAULT_IMAGE_ANALYSIS_HEIGHT)) 81 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) 82 | .build(); 83 | mImageAnalysis.setAnalyzer(mCameraExecutor, this); 84 | } 85 | return mImageAnalysis; 86 | } 87 | 88 | private void startCamera() { 89 | final ListenableFuture future = ProcessCameraProvider.getInstance(mContext); 90 | future.addListener(() -> { 91 | try { 92 | ProcessCameraProvider provider = future.get(); 93 | Preview preview = new Preview.Builder().build(); 94 | PreviewView previewView = findViewById(R.id.previewView); 95 | preview.setSurfaceProvider(previewView.getSurfaceProvider()); 96 | provider.unbindAll(); 97 | provider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, 98 | getImageAnalysis(), preview); 99 | } catch (ExecutionException | InterruptedException e) { 100 | LogHelper.e(TAG, "Bind camera failed: " + e.getMessage()); 101 | Toast.makeText(mContext.getApplicationContext(), R.string.scan_qr_code_camera_error, Toast.LENGTH_SHORT).show(); 102 | e.printStackTrace(); 103 | finishActivity(); 104 | } 105 | }, mMainExecutor); 106 | } 107 | 108 | @androidx.camera.core.ExperimentalGetImage 109 | @Override 110 | public void analyze(@NonNull ImageProxy imageProxy) { 111 | Image image = imageProxy.getImage(); 112 | if (image == null) { 113 | imageProxy.close(); 114 | return; 115 | } 116 | mScanner.process(InputImage.fromMediaImage(image, 0)).addOnSuccessListener(barcodes -> { 117 | barcodes.stream().filter(barcode -> { 118 | String rawValue = barcode.getRawValue(); 119 | return rawValue != null && !TextUtils.isEmpty(rawValue.trim()); 120 | }).findFirst().ifPresent(barcode -> { 121 | mImageAnalysis.clearAnalyzer(); 122 | returnScanResult(barcode.getRawValue()); 123 | }); 124 | }).addOnCanceledListener(() -> { 125 | mImageAnalysis.clearAnalyzer(); 126 | returnEmptyResult(); 127 | }) 128 | .addOnFailureListener(exception -> { 129 | LogHelper.i(TAG, "onFailed " + exception.getMessage()); 130 | mImageAnalysis.clearAnalyzer(); 131 | imageProxy.close(); 132 | showScanQRCodeError(); 133 | returnEmptyResult(); 134 | }).addOnCompleteListener(barcodes -> { 135 | imageProxy.close(); 136 | }); 137 | } 138 | 139 | private void returnScanResult(String qrCode) { 140 | Activity activity = requireActivity(); 141 | activity.setResult(Activity.RESULT_OK, new Intent().putExtra(KEY_SCAN_CONTENT, qrCode)); 142 | activity.finish(); 143 | } 144 | 145 | private void returnEmptyResult() { 146 | Activity activity = requireActivity(); 147 | activity.setResult(Activity.RESULT_CANCELED); 148 | activity.finish(); 149 | } 150 | 151 | private void showScanQRCodeError() { 152 | final Context context = mContext.getApplicationContext(); 153 | runOnUiThread(() -> Toast.makeText(context, R.string.scan_qr_code_camera_error, 154 | Toast.LENGTH_SHORT).show()); 155 | } 156 | 157 | @Override 158 | public void onDestroy() { 159 | super.onDestroy(); 160 | if (mCameraExecutor != null) { 161 | mCameraExecutor.shutdown(); 162 | mCameraExecutor = null; 163 | } 164 | mMainExecutor = null; 165 | mScanner = null; 166 | mImageAnalysis = null; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/servers/ItemVerticalMoveCallback.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.servers; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | import androidx.recyclerview.widget.ItemTouchHelper; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | 9 | public class ItemVerticalMoveCallback extends ItemTouchHelper.Callback { 10 | private final ItemTouchHelperContract mItemTouchHelperContract; 11 | 12 | @Override 13 | public boolean isLongPressDragEnabled() { 14 | return true; 15 | } 16 | 17 | @Override 18 | public boolean isItemViewSwipeEnabled() { 19 | return false; 20 | } 21 | 22 | public ItemVerticalMoveCallback(ItemTouchHelperContract itemTouchHelperContract) { 23 | mItemTouchHelperContract = itemTouchHelperContract; 24 | } 25 | 26 | @Override 27 | public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { 28 | return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0); 29 | } 30 | 31 | @Override 32 | public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { 33 | int srcPos = viewHolder.getBindingAdapterPosition(); 34 | int destPos = target.getBindingAdapterPosition(); 35 | if (srcPos == RecyclerView.NO_POSITION || destPos == RecyclerView.NO_POSITION) { 36 | return false; 37 | } 38 | mItemTouchHelperContract.onRowMove(srcPos, destPos); 39 | return true; 40 | } 41 | 42 | @Override 43 | public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) { 44 | if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { 45 | mItemTouchHelperContract.onRowSelected(viewHolder); 46 | } 47 | super.onSelectedChanged(viewHolder, actionState); 48 | } 49 | 50 | @Override 51 | public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { 52 | super.clearView(recyclerView, viewHolder); 53 | mItemTouchHelperContract.onRowClear(viewHolder); 54 | } 55 | 56 | @Override 57 | public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { 58 | } 59 | 60 | public interface ItemTouchHelperContract { 61 | void onRowMove(int srcPosition, int destPosition); 62 | 63 | void onRowSelected(RecyclerView.ViewHolder viewHolder); 64 | 65 | void onRowClear(RecyclerView.ViewHolder viewHolder); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/servers/SubscribeSettingDialog.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.servers; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.text.Editable; 6 | import android.view.View; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.google.android.material.textfield.TextInputEditText; 11 | 12 | import io.github.trojan_gfw.igniter.R; 13 | 14 | public class SubscribeSettingDialog extends Dialog implements View.OnClickListener { 15 | private TextInputEditText mSubscribeUrlTiet; 16 | private OnButtonClickListener mListener; 17 | 18 | public SubscribeSettingDialog(@NonNull Context context) { 19 | super(context); 20 | setContentView(R.layout.dialog_subscribe_setting); 21 | mSubscribeUrlTiet = findViewById(R.id.subscribeLinkUrlTiet); 22 | findViewById(R.id.subscribeSettingConfirmBtn).setOnClickListener(this); 23 | findViewById(R.id.subscribeSettingCancelBtn).setOnClickListener(this); 24 | } 25 | 26 | public void setOnButtonClickListener(OnButtonClickListener listener) { 27 | mListener = listener; 28 | } 29 | 30 | public void setSubscribeUrl(String url) { 31 | mSubscribeUrlTiet.setText(url); 32 | } 33 | 34 | @Override 35 | public void onClick(View v) { 36 | if (mListener == null) return; 37 | switch (v.getId()) { 38 | case R.id.subscribeSettingConfirmBtn: 39 | Editable editable = mSubscribeUrlTiet.getText(); 40 | String text; 41 | if (editable != null) { 42 | text = editable.toString(); 43 | } else { 44 | text = ""; 45 | } 46 | mListener.onConfirm(text); 47 | break; 48 | case R.id.subscribeSettingCancelBtn: 49 | mListener.onCancel(); 50 | break; 51 | default: 52 | break; 53 | } 54 | } 55 | 56 | public interface OnButtonClickListener { 57 | void onConfirm(String url); 58 | 59 | void onCancel(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /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 | private static final String KEY_PROXY_ON = "proxy_on"; 19 | private static final String KEY_PROXY_HOST = "proxy_host"; 20 | private static final String KEY_PROXY_PORT = "proxy_port"; 21 | 22 | public static Intent create(Context context, boolean proxyOn, String proxyHost, long proxyPort) { 23 | Intent intent = new Intent(context, ServerListActivity.class); 24 | intent.putExtra(KEY_PROXY_ON, proxyOn); 25 | intent.putExtra(KEY_PROXY_HOST, proxyHost); 26 | intent.putExtra(KEY_PROXY_PORT, proxyPort); 27 | return intent; 28 | } 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_server_list); 34 | 35 | FragmentManager fm = getSupportFragmentManager(); 36 | ServerListFragment fragment = (ServerListFragment) fm.findFragmentByTag(ServerListFragment.TAG); 37 | if (fragment == null) { 38 | fragment = ServerListFragment.newInstance(); 39 | } 40 | Intent intent = getIntent(); 41 | boolean proxyOn = intent.getBooleanExtra(KEY_PROXY_ON, false); 42 | String proxyHost = intent.getStringExtra(KEY_PROXY_HOST); 43 | long proxyPort = intent.getLongExtra(KEY_PROXY_PORT, 0L); 44 | new ServerListPresenter(fragment, new ServerListDataManager(Globals.getTrojanConfigListPath(), proxyOn, proxyHost, proxyPort)); 45 | fm.beginTransaction() 46 | .replace(R.id.parent_fl, fragment, ServerListFragment.TAG) 47 | .commitAllowingStateLoss(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /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 | import java.util.Set; 8 | 9 | import io.github.trojan_gfw.igniter.TrojanConfig; 10 | import io.github.trojan_gfw.igniter.common.mvp.BasePresenter; 11 | import io.github.trojan_gfw.igniter.common.mvp.BaseView; 12 | 13 | public interface ServerListContract { 14 | interface Presenter extends BasePresenter { 15 | void addServerConfig(String trojanUrl); 16 | 17 | void handleServerSelection(TrojanConfig config); 18 | 19 | void gotoScanQRCode(boolean fromGallery); 20 | 21 | void displayImportFileDescription(); 22 | 23 | void hideImportFileDescription(); 24 | 25 | void importConfigFromFile(); 26 | 27 | void parseConfigsInFileStream(Context context, Uri fileUri); 28 | 29 | void exportServerListToFile(); 30 | 31 | void batchOperateServerList(); 32 | 33 | void exitServerListBatchOperation(); 34 | 35 | void selectServer(TrojanConfig config, boolean checked); 36 | 37 | void selectAll(List configList); 38 | 39 | void deselectAll(List configList); 40 | 41 | void batchDelete(); 42 | 43 | void displaySubscribeSettings(); 44 | 45 | void updateSubscribeServers(); 46 | 47 | void saveSubscribeSettings(String url); 48 | 49 | void hideSubscribeSettings(); 50 | 51 | void saveServerList(List configList); 52 | 53 | void pingAllProxyServer(List configList); 54 | } 55 | 56 | interface View extends BaseView { 57 | void showAddTrojanConfigSuccess(); 58 | 59 | void askTheWayToScanQRCode(); 60 | 61 | void showQRCodeScanError(String scanContent); 62 | 63 | void selectServerConfig(TrojanConfig config); 64 | 65 | void showServerConfigList(List configs); 66 | 67 | void removeServerConfig(TrojanConfig config, int pos); 68 | 69 | void scanQRCodeFromCamera(); 70 | 71 | void scanQRCodeFromGallery(); 72 | 73 | void showImportFileDescription(); 74 | 75 | void dismissImportFileDescription(); 76 | 77 | void openFileChooser(); 78 | 79 | void showExportServerListSuccess(); 80 | 81 | void showExportServerListFailure(); 82 | 83 | void showServerListBatchOperation(); 84 | 85 | void hideServerListBatchOperation(); 86 | 87 | void selectAllServers(); 88 | 89 | void deselectAllServers(); 90 | 91 | void showBatchDeletionSuccess(); 92 | 93 | void showLoading(); 94 | 95 | void dismissLoading(); 96 | 97 | void batchDelete(Set configList); 98 | 99 | void showSubscribeSettings(String url); 100 | 101 | void dismissSubscribeSettings(); 102 | 103 | void showSubscribeUpdateSuccess(); 104 | 105 | void showSubscribeUpdateFailed(); 106 | 107 | void setPingServerDelayTime(TrojanConfig config, float timeout); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /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 android.content.Context; 4 | import android.net.Uri; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.WorkerThread; 8 | 9 | import com.stealthcopter.networktools.ping.PingStats; 10 | 11 | import java.util.Collection; 12 | import java.util.List; 13 | 14 | import io.github.trojan_gfw.igniter.TrojanConfig; 15 | 16 | public interface ServerListDataSource { 17 | @WorkerThread 18 | List loadServerConfigList(); 19 | 20 | @WorkerThread 21 | void deleteServerConfig(TrojanConfig config); 22 | 23 | @WorkerThread 24 | void batchDeleteServerConfigs(Collection configs); 25 | 26 | @WorkerThread 27 | void saveServerConfig(TrojanConfig config); 28 | 29 | @WorkerThread 30 | void replaceServerConfigs(List list); 31 | 32 | @WorkerThread 33 | void requestSubscribeServerConfigs(String url, @NonNull Callback callback); 34 | 35 | @WorkerThread 36 | void pingTrojanConfigServer(TrojanConfig config, @NonNull PingCallback callback); 37 | 38 | /** 39 | * Parse trojan configs from {@param fileUri}. Combine with current trojan config list and return 40 | * the complete list. 41 | * 42 | * @param context Context 43 | * @param fileUri File Uri 44 | * @return List of all trojan configs. 45 | */ 46 | @WorkerThread 47 | List importServersFromFile(Context context, Uri fileUri); 48 | 49 | @WorkerThread 50 | boolean exportServers(String exportPath); 51 | 52 | interface Callback { 53 | void onSuccess(); 54 | 55 | void onFailed(); 56 | } 57 | 58 | interface PingCallback { 59 | void onSuccess(TrojanConfig config, PingStats pingStats); 60 | 61 | void onFailed(TrojanConfig config); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/servers/data/TrojanConfigWrapper.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.servers.data; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import io.github.trojan_gfw.igniter.TrojanConfig; 6 | import io.github.trojan_gfw.igniter.servers.data.ServerListDataManager; 7 | 8 | public class TrojanConfigWrapper extends TrojanConfig { 9 | private final TrojanConfig mDelegate; 10 | private boolean mSelected; 11 | private float mPingDelayTime = ServerListDataManager.SERVER_STATUS_INIT; 12 | 13 | public TrojanConfigWrapper(TrojanConfig delegate) { 14 | mDelegate = delegate; 15 | } 16 | 17 | public TrojanConfig getDelegate() { 18 | return mDelegate; 19 | } 20 | 21 | public boolean isSelected() { 22 | return mSelected; 23 | } 24 | 25 | public void setSelected(boolean selected) { 26 | mSelected = selected; 27 | } 28 | 29 | @Override 30 | public String generateTrojanConfigJSON() { 31 | return mDelegate.generateTrojanConfigJSON(); 32 | } 33 | 34 | @Override 35 | public void fromJSON(String jsonStr) { 36 | mDelegate.fromJSON(jsonStr); 37 | } 38 | 39 | @Override 40 | public void copyFrom(TrojanConfig that) { 41 | mDelegate.copyFrom(that); 42 | } 43 | 44 | @Override 45 | public boolean isValidRunningConfig() { 46 | return mDelegate.isValidRunningConfig(); 47 | } 48 | 49 | @Override 50 | public String getLocalAddr() { 51 | return mDelegate.getLocalAddr(); 52 | } 53 | 54 | @Override 55 | public TrojanConfig setLocalAddr(String localAddr) { 56 | return mDelegate.setLocalAddr(localAddr); 57 | } 58 | 59 | @Override 60 | public int getLocalPort() { 61 | return mDelegate.getLocalPort(); 62 | } 63 | 64 | @Override 65 | public TrojanConfig setLocalPort(int localPort) { 66 | return mDelegate.setLocalPort(localPort); 67 | } 68 | 69 | @Override 70 | public String getRemoteServerRemark() { 71 | return mDelegate.getRemoteServerRemark(); 72 | } 73 | 74 | @Override 75 | public TrojanConfig setRemoteServerRemark(String remoteServerRemark) { 76 | return mDelegate.setRemoteServerRemark(remoteServerRemark); 77 | } 78 | 79 | @Override 80 | public String getIdentifier() { 81 | return mDelegate.getIdentifier(); 82 | } 83 | 84 | @Override 85 | public String getRemoteAddr() { 86 | return mDelegate.getRemoteAddr(); 87 | } 88 | 89 | @Override 90 | public TrojanConfig setRemoteAddr(String remoteAddr) { 91 | return mDelegate.setRemoteAddr(remoteAddr); 92 | } 93 | 94 | @Override 95 | public int getRemotePort() { 96 | return mDelegate.getRemotePort(); 97 | } 98 | 99 | @Override 100 | public TrojanConfig setRemotePort(int remotePort) { 101 | return mDelegate.setRemotePort(remotePort); 102 | } 103 | 104 | @Override 105 | public String getPassword() { 106 | return mDelegate.getPassword(); 107 | } 108 | 109 | @Override 110 | public TrojanConfig setPassword(String password) { 111 | return mDelegate.setPassword(password); 112 | } 113 | 114 | @Override 115 | public boolean getVerifyCert() { 116 | return mDelegate.getVerifyCert(); 117 | } 118 | 119 | @Override 120 | public TrojanConfig setVerifyCert(boolean verifyCert) { 121 | return mDelegate.setVerifyCert(verifyCert); 122 | } 123 | 124 | @Override 125 | public String getCaCertPath() { 126 | return mDelegate.getCaCertPath(); 127 | } 128 | 129 | @Override 130 | public TrojanConfig setCaCertPath(String caCertPath) { 131 | return mDelegate.setCaCertPath(caCertPath); 132 | } 133 | 134 | @Override 135 | public boolean getEnableIpv6() { 136 | return mDelegate.getEnableIpv6(); 137 | } 138 | 139 | @Override 140 | public TrojanConfig setEnableIpv6(boolean enableIpv6) { 141 | return mDelegate.setEnableIpv6(enableIpv6); 142 | } 143 | 144 | @Override 145 | public String getCipherList() { 146 | return mDelegate.getCipherList(); 147 | } 148 | 149 | @Override 150 | public TrojanConfig setCipherList(String cipherList) { 151 | return mDelegate.setCipherList(cipherList); 152 | } 153 | 154 | @Override 155 | public String getTls13CipherList() { 156 | return mDelegate.getTls13CipherList(); 157 | } 158 | 159 | @Override 160 | public TrojanConfig setTls13CipherList(String tls13CipherList) { 161 | return mDelegate.setTls13CipherList(tls13CipherList); 162 | } 163 | 164 | @Override 165 | public boolean equals(@Nullable Object obj) { 166 | return mDelegate.equals(obj); 167 | } 168 | 169 | public float getPingDelayTime() { 170 | return mPingDelayTime; 171 | } 172 | 173 | public void setPingDelayTime(float pingDelayTime) { 174 | this.mPingDelayTime = pingDelayTime; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/settings/InputEntryView.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.settings; 2 | 3 | import android.content.Context; 4 | import android.text.Editable; 5 | import android.text.TextWatcher; 6 | import android.util.AttributeSet; 7 | import android.view.Gravity; 8 | import android.widget.EditText; 9 | import android.widget.FrameLayout; 10 | import android.widget.ImageView; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | 15 | import com.google.android.material.textfield.TextInputEditText; 16 | 17 | import io.github.trojan_gfw.igniter.R; 18 | import io.github.trojan_gfw.igniter.common.utils.DisplayUtils; 19 | 20 | public class InputEntryView extends FrameLayout { 21 | private TextInputEditText mEditText; 22 | private Listener mListener; 23 | 24 | public InputEntryView(@NonNull Context context) { 25 | super(context); 26 | init(context); 27 | } 28 | 29 | public InputEntryView(@NonNull Context context, @Nullable AttributeSet attrs) { 30 | super(context, attrs); 31 | init(context); 32 | } 33 | 34 | public InputEntryView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | init(context); 37 | } 38 | 39 | private void init(Context context) { 40 | mEditText = new TextInputEditText(context); 41 | int width = (int) context.getResources().getDimension(R.dimen.settings_dns_input_width); 42 | mEditText.setLayoutParams(new LayoutParams(width, LayoutParams.WRAP_CONTENT)); 43 | ImageView deleteIv = new ImageView(context); 44 | deleteIv.setImageResource(R.drawable.icon_remove); 45 | int size = (int) context.getResources().getDimension(R.dimen.settings_dns_delete_btn_size); 46 | LayoutParams lp = new LayoutParams(size, size); 47 | lp.gravity = Gravity.END | Gravity.CENTER_VERTICAL; 48 | deleteIv.setLayoutParams(lp); 49 | deleteIv.setScaleType(ImageView.ScaleType.FIT_XY); 50 | addView(mEditText); 51 | addView(deleteIv); 52 | deleteIv.setOnClickListener(v-> { 53 | if (mListener != null) { 54 | mListener.onDelete(this); 55 | } 56 | }); 57 | mEditText.addTextChangedListener(new TextWatcher() { 58 | @Override 59 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 60 | mEditText.setError(""); 61 | } 62 | 63 | @Override 64 | public void onTextChanged(CharSequence s, int start, int before, int count) { 65 | 66 | } 67 | 68 | @Override 69 | public void afterTextChanged(Editable s) { 70 | 71 | } 72 | }); 73 | } 74 | 75 | public void setText(CharSequence text) { 76 | mEditText.setText(text); 77 | mEditText.setError(null); 78 | } 79 | 80 | @NonNull 81 | public CharSequence getText() { 82 | CharSequence cs = mEditText.getText(); 83 | return cs == null ? "" : cs; 84 | } 85 | 86 | public void setListener(Listener listener) { 87 | mListener = listener; 88 | } 89 | 90 | public void setError(String error) { 91 | mEditText.setError(error); 92 | } 93 | 94 | public interface Listener { 95 | void onDelete(InputEntryView view); 96 | } 97 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/settings/activity/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.settings.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.R; 10 | import io.github.trojan_gfw.igniter.common.app.BaseAppCompatActivity; 11 | import io.github.trojan_gfw.igniter.settings.data.SettingsDataManager; 12 | import io.github.trojan_gfw.igniter.settings.fragment.SettingsFragment; 13 | import io.github.trojan_gfw.igniter.settings.presenter.SettingsPresenter; 14 | 15 | public class SettingsActivity extends BaseAppCompatActivity { 16 | 17 | public static Intent create(Context context) { 18 | return new Intent(context, SettingsActivity.class); 19 | } 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_settings); 25 | 26 | FragmentManager fm = getSupportFragmentManager(); 27 | SettingsFragment fragment = (SettingsFragment) fm.findFragmentByTag(SettingsFragment.TAG); 28 | if (fragment == null) { 29 | fragment = SettingsFragment.newInstance(); 30 | } 31 | new SettingsPresenter(fragment, new SettingsDataManager(this)); 32 | fm.beginTransaction().replace(R.id.settings_parent_fl, fragment, SettingsFragment.TAG) 33 | .commitAllowingStateLoss(); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/settings/contract/SettingsContract.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.settings.contract; 2 | 3 | import androidx.annotation.AnyThread; 4 | import androidx.annotation.NonNull; 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 | 11 | public interface SettingsContract { 12 | interface Presenter extends BasePresenter { 13 | void addDNSInput(); 14 | void removeDNSInput(int viewIndex); 15 | void saveSettings(@NonNull List dnsList, String port); 16 | void exit(); 17 | } 18 | interface View extends BaseView { 19 | @AnyThread 20 | void showExtraDNSList(@NonNull List dnsList); 21 | void showDNSFormatError(int viewIndex); 22 | @AnyThread 23 | void showSettingsSaved(); 24 | void appendDNSInput(); 25 | void removeDNSInput(int index); 26 | void showExitConfirm(); 27 | void showPortNumberError(); 28 | @AnyThread 29 | void showLoading(); 30 | @AnyThread 31 | void dismissLoading(); 32 | void quit(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/settings/data/ISettingsDataManager.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.settings.data; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.WorkerThread; 5 | 6 | import java.util.List; 7 | 8 | public interface ISettingsDataManager { 9 | @NonNull 10 | @WorkerThread 11 | List loadExtraDNSList(); 12 | @WorkerThread 13 | void saveExtraDNSList(@NonNull List dnsList); 14 | void saveFixedPort(int port); 15 | 16 | /*** 17 | * Load fixed port from settings. If no fixed port is specified, returns -1. 18 | */ 19 | int loadFixedPort(); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/settings/data/SettingsDataManager.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.settings.data; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.text.TextUtils; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import org.json.JSONArray; 10 | import org.json.JSONException; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | import io.github.trojan_gfw.igniter.common.constants.Constants; 17 | import io.github.trojan_gfw.igniter.common.utils.PreferenceUtils; 18 | 19 | public class SettingsDataManager implements ISettingsDataManager { 20 | private final Context mContext; 21 | 22 | public SettingsDataManager(Context context) { 23 | super(); 24 | mContext = context.getApplicationContext(); 25 | } 26 | 27 | @NonNull 28 | @Override 29 | public List loadExtraDNSList() { 30 | String dnsJsonArrayString = PreferenceUtils.getStringPreference(mContext.getContentResolver(), 31 | Uri.parse(Constants.PREFERENCE_URI), Constants.PREFERENCE_KEY_EXTRA_DNS, ""); 32 | if (TextUtils.isEmpty(dnsJsonArrayString)) { 33 | return Collections.emptyList(); 34 | } 35 | try { 36 | JSONArray jsonArray = new JSONArray(dnsJsonArrayString); 37 | int len = jsonArray.length(); 38 | List list = new ArrayList<>(len); 39 | for (int i = 0; i < len; i++) { 40 | list.add(jsonArray.getString(i)); 41 | } 42 | return list; 43 | } catch (JSONException e) { 44 | e.printStackTrace(); 45 | } 46 | return Collections.emptyList(); 47 | } 48 | 49 | @Override 50 | public void saveExtraDNSList(@NonNull List dnsList) { 51 | JSONArray jsonArray = new JSONArray(); 52 | for (String dns : dnsList) { 53 | jsonArray.put(dns); 54 | } 55 | PreferenceUtils.putStringPreference(mContext.getContentResolver(), 56 | Uri.parse(Constants.PREFERENCE_URI), Constants.PREFERENCE_KEY_EXTRA_DNS, 57 | jsonArray.toString()); 58 | } 59 | 60 | @Override 61 | public void saveFixedPort(int port) { 62 | PreferenceUtils.putIntPreference(mContext.getContentResolver(), 63 | Uri.parse(Constants.PREFERENCE_URI), 64 | Constants.PREFERENCE_KEY_FIXED_PORT, port); 65 | } 66 | 67 | @Override 68 | public int loadFixedPort() { 69 | return PreferenceUtils.getIntPreference(mContext.getContentResolver(), 70 | Uri.parse(Constants.PREFERENCE_URI), 71 | Constants.PREFERENCE_KEY_FIXED_PORT, -1); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/trojan_gfw/igniter/settings/presenter/SettingsPresenter.java: -------------------------------------------------------------------------------- 1 | package io.github.trojan_gfw.igniter.settings.presenter; 2 | 3 | import android.text.TextUtils; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.List; 8 | import java.util.regex.Pattern; 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.settings.contract.SettingsContract; 13 | import io.github.trojan_gfw.igniter.settings.data.ISettingsDataManager; 14 | 15 | public class SettingsPresenter implements SettingsContract.Presenter { 16 | private static final int MIN_PORT = 49152; 17 | private static final int MAX_PORT = 65535; 18 | private final SettingsContract.View mView; 19 | private final ISettingsDataManager mDataManager; 20 | private boolean mHasModifiedDNS; 21 | 22 | public SettingsPresenter(SettingsContract.View view, ISettingsDataManager dataManager) { 23 | mView = view; 24 | mDataManager = dataManager; 25 | view.setPresenter(this); 26 | } 27 | 28 | @Override 29 | public void saveSettings(@NonNull List dnsList, String portStr) { 30 | mView.showLoading(); 31 | Threads.instance().runOnWorkThread(new Task() { 32 | @Override 33 | public void onRun() { 34 | boolean savePortSuccess = savePortSettings(portStr); 35 | if (!savePortSuccess) { 36 | mView.dismissLoading(); 37 | mView.showPortNumberError(); 38 | return; 39 | } 40 | if (saveDNSList(dnsList)) { 41 | mView.showSettingsSaved(); 42 | } 43 | mView.dismissLoading(); 44 | } 45 | }); 46 | } 47 | 48 | private boolean saveDNSList(@NonNull List dnsList) { 49 | boolean error = false; 50 | Pattern pattern = Pattern.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"); 51 | for (int i = 0, size = dnsList.size(); i < size; i++) { 52 | if (!pattern.matcher(dnsList.get(i)).matches()) { 53 | mView.dismissLoading(); 54 | mView.showDNSFormatError(i); 55 | error = true; 56 | } 57 | } 58 | if (error) { 59 | return false; 60 | } 61 | mDataManager.saveExtraDNSList(dnsList); 62 | mHasModifiedDNS = false; 63 | return true; 64 | } 65 | 66 | private boolean savePortSettings(String portStr) { 67 | if (TextUtils.isEmpty(portStr.trim())) { 68 | mDataManager.saveFixedPort(-1); 69 | return true; 70 | } 71 | try { 72 | int port = Integer.parseInt(portStr); 73 | if (port >= MIN_PORT && port <= MAX_PORT) { 74 | mDataManager.saveFixedPort(port); 75 | return true; 76 | } 77 | } catch (NumberFormatException e) { 78 | e.printStackTrace(); 79 | } 80 | return false; 81 | } 82 | 83 | @Override 84 | public void addDNSInput() { 85 | mView.appendDNSInput(); 86 | mHasModifiedDNS = true; 87 | } 88 | 89 | @Override 90 | public void removeDNSInput(int index) { 91 | mView.removeDNSInput(index); 92 | mHasModifiedDNS = true; 93 | } 94 | 95 | private boolean configIsDirty() { 96 | return mHasModifiedDNS; 97 | } 98 | 99 | @Override 100 | public void exit() { 101 | if (configIsDirty()) { 102 | mView.showExitConfirm(); 103 | } else { 104 | mView.quit(); 105 | } 106 | } 107 | 108 | @Override 109 | public void start() { 110 | mView.showLoading(); 111 | Threads.instance().runOnWorkThread(new Task() { 112 | @Override 113 | public void onRun() { 114 | List dnsList = mDataManager.loadExtraDNSList(); 115 | mView.showExtraDNSList(dnsList); 116 | mView.dismissLoading(); 117 | } 118 | }); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /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 | if (BuildConfig.DEBUG) { 32 | TrojanHelper.ShowConfig(Globals.getTrojanConfigPath()); 33 | } 34 | return cacheConfig.isValidRunningConfig(); 35 | } 36 | 37 | public static boolean isVPNServiceConsented(Context context) { 38 | return VpnService.prepare(context.getApplicationContext()) == null; 39 | } 40 | 41 | public static void startProxyService(Context context) { 42 | ContextCompat.startForegroundService(context, new Intent(context, ProxyService.class)); 43 | } 44 | 45 | public static void startLauncherActivity(Context context) { 46 | Intent intent = new Intent(context, MainActivity.class); 47 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 48 | context.startActivity(intent); 49 | } 50 | 51 | public static void stopProxyService(Context context) { 52 | Intent intent = new Intent(context.getString(R.string.stop_service)); 53 | intent.setPackage(context.getPackageName()); 54 | context.sendBroadcast(intent); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-hdpi/ic_action_link.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-hdpi/ic_action_name.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-hdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-hdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-hdpi/ic_tile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-hdpi/qr_code.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-mdpi/ic_action_link.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-mdpi/ic_action_name.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-mdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-mdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-mdpi/ic_tile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-mdpi/qr_code.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xhdpi/ic_action_link.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xhdpi/ic_action_name.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xhdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xhdpi/ic_tile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xhdpi/qr_code.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/ic_action_link.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/ic_action_name.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/ic_tile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/icon_add.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/icon_remove.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxhdpi/qr_code.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_action_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxxhdpi/ic_action_link.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_action_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxxhdpi/ic_action_name.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxxhdpi/ic_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxxhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/drawable-xxxhdpi/ic_tile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/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/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/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_scan_qrcode.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_server_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.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/dialog_subscribe_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 25 | 26 | 27 | 36 | 37 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_exempt_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 23 | 24 | 29 | 30 | 35 | 36 | 37 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_scan_qr_code.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_server_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 19 | 27 | 28 | 36 | 37 | 44 | 45 | 52 | 53 | 54 | 60 | 61 | 62 | 71 | 72 | 77 | 78 | 81 | 82 | 88 | 89 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /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 | 12 | 13 | 16 | 17 | 25 | 26 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_exempt_app.xml: -------------------------------------------------------------------------------- 1 | 2 |

4 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | 15 | 19 | 20 | 26 | 31 | 36 | 42 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_server_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | 12 | 13 | 16 | 17 | 21 | 22 | 25 | 26 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /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/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/raw/country.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/app/src/main/res/raw/country.mmdb -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Igniter 3 | 4 | 删除 5 | 提示 6 | 确认 7 | 取消 8 | 远不 9 | 更新 10 | 保存成功 11 | 12 | 远程服务备注 13 | 远程地址 14 | 远程服务 SNI 15 | 远程端口 16 | 443 17 | 密码 18 | 连接 19 | 正在打开连接 20 | 断开连接 21 | 正在断开连接 22 | 验证证书 23 | 过滤大陆域名/IP 24 | 允许局域网访问 25 | 开启 IPv6 26 | io.github.trojan_gfw.igniter.STOP_SERVICE 27 | io.github.trojan_gfw.igniter.BIND_SERVICE 28 | 连接 %1$s 用时 %2$sms. 29 | 连接失败 %1$s: %2$s 30 | 无效配置 31 | igniter_notify_chan 32 | Igniter 通知 33 | 开启服务 34 | 启用新的过滤应用配置请重新应用 35 | 导入粘贴板中的 Trojan 地址吗? 36 | 37 | 38 | 测试连接 39 | 在logcat中显示开发信息 40 | 41 | 导入配置文件 42 | 选择文本文件 43 | 从文件中导入 44 | 测试所有代理服务器 45 | @string/common_delete 46 | 缺少相机权限 47 | @string/common_save_success 48 | 保存失败 49 | 50 | 51 | 为了保存或修改过滤应用配置列表,Igniter 需要读写外部存储空间权限。 52 | 卸载了 Igniter 后,进行重新安装时,也可以用此来恢复。 53 | 否则将无法使用此功能。 54 | 55 | 56 | 请先在 Igniter 应用程序设置中授予读写外部存储权限。 57 | 58 | 加载配置 59 | 60 | 添加服务成功 61 | 无法识别 trojan 地址: %s 62 | 扫描二维码 63 | 相机错误 64 | 65 | 保存 66 | 过滤应用 67 | 搜索应用 68 | 退出 69 | 加载中… 70 | 确定不保存过滤应用列表就退出吗? 71 | 72 | 在外部存储中有过滤应用相关配置,是否需要迁移? 73 | 74 | 保存配置 75 | 分享链接 76 | 关于 77 | 78 | 79 | 项目地址 80 | 关于 81 | 鸣谢 82 | 许可 83 | 84 | 85 | 版本 86 | 开发者 87 | 88 | 89 | Dreamacro/clash 90 | eycorsican/go-tun2socks 91 | bingoogolapple/BGAQRCode-Android 92 | 93 | 关于 94 | 95 | 96 | Trojan 服务不可用 97 | Trojan 服务错误 98 | 代理服务暂未开启 99 | 代理应用模式 100 | 屏蔽应用模式 101 | 导出配置 102 | 成功导出配置到 %s 103 | 导出配置失败 104 | 全选 105 | 删除成功 106 | 批量… 107 | 全不选 108 | 订阅链接 109 | 订阅设置 110 | 订阅更新 111 | 更新成功 112 | 更新失败,请检查订阅设置或者尝试开启代理后再更新,也可能是Igniter不支持该订阅信息的格式 113 | 退出 114 | 无法获取图片 115 | 开相机扫描二维码 116 | 从相册读取二维码 117 | 缺少读取外部存储权限 118 | 设置 119 | 尚未保存设置,确认退出吗? 120 | 保存成功 121 | 非法DNS 122 | 保存 123 | 设置 124 | 代理端口: %1$s 125 | 代理端口 %1$s 已复制到剪贴板 126 | 固定端口 127 | 端口范围:49152~65535,使用随机端口的话不需要填数字 128 | 仅当允许局域网访问时生效\n范围:49152~65535,使用随机端口的话不需要填数字 129 | 130 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 1.0.0.0/8 10 | 2.0.0.0/7 11 | 4.0.0.0/6 12 | 8.0.0.0/7 13 | 11.0.0.0/8 14 | 12.0.0.0/6 15 | 16.0.0.0/4 16 | 32.0.0.0/3 17 | 64.0.0.0/3 18 | 96.0.0.0/6 19 | 100.0.0.0/10 20 | 100.128.0.0/9 21 | 101.0.0.0/8 22 | 102.0.0.0/7 23 | 104.0.0.0/5 24 | 112.0.0.0/5 25 | 120.0.0.0/6 26 | 124.0.0.0/7 27 | 126.0.0.0/8 28 | 128.0.0.0/3 29 | 160.0.0.0/5 30 | 168.0.0.0/8 31 | 169.0.0.0/9 32 | 169.128.0.0/10 33 | 169.192.0.0/11 34 | 169.224.0.0/12 35 | 169.240.0.0/13 36 | 169.248.0.0/14 37 | 169.252.0.0/15 38 | 169.255.0.0/16 39 | 170.0.0.0/7 40 | 172.0.0.0/12 41 | 172.32.0.0/11 42 | 172.64.0.0/10 43 | 172.128.0.0/9 44 | 173.0.0.0/8 45 | 174.0.0.0/7 46 | 176.0.0.0/4 47 | 192.0.1.0/24 48 | 192.0.3.0/24 49 | 192.0.4.0/22 50 | 192.0.8.0/21 51 | 192.0.16.0/20 52 | 192.0.32.0/19 53 | 192.0.64.0/18 54 | 192.0.128.0/17 55 | 192.1.0.0/16 56 | 192.2.0.0/15 57 | 192.4.0.0/14 58 | 192.8.0.0/13 59 | 192.16.0.0/12 60 | 192.32.0.0/11 61 | 192.64.0.0/10 62 | 192.128.0.0/11 63 | 192.160.0.0/13 64 | 192.169.0.0/16 65 | 192.170.0.0/15 66 | 192.172.0.0/14 67 | 192.176.0.0/12 68 | 192.192.0.0/10 69 | 193.0.0.0/8 70 | 194.0.0.0/7 71 | 196.0.0.0/7 72 | 198.0.0.0/12 73 | 198.16.0.0/15 74 | 198.20.0.0/14 75 | 198.24.0.0/13 76 | 198.32.0.0/12 77 | 198.48.0.0/15 78 | 198.50.0.0/16 79 | 198.51.0.0/18 80 | 198.51.64.0/19 81 | 198.51.96.0/22 82 | 198.51.101.0/24 83 | 198.51.102.0/23 84 | 198.51.104.0/21 85 | 198.51.112.0/20 86 | 198.51.128.0/17 87 | 198.52.0.0/14 88 | 198.56.0.0/13 89 | 198.64.0.0/10 90 | 198.128.0.0/9 91 | 199.0.0.0/8 92 | 200.0.0.0/7 93 | 202.0.0.0/8 94 | 203.0.0.0/18 95 | 203.0.64.0/19 96 | 203.0.96.0/20 97 | 203.0.112.0/24 98 | 203.0.114.0/23 99 | 203.0.116.0/22 100 | 203.0.120.0/21 101 | 203.0.128.0/17 102 | 203.1.0.0/16 103 | 203.2.0.0/15 104 | 203.4.0.0/14 105 | 203.8.0.0/13 106 | 203.16.0.0/12 107 | 203.32.0.0/11 108 | 203.64.0.0/10 109 | 203.128.0.0/9 110 | 204.0.0.0/6 111 | 208.0.0.0/4 112 | 224.0.0.0/4 113 | 114 | 115 | 116 | @string/scan_qr_code_from_camera 117 | @string/scan_qr_code_from_gallery 118 | 119 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | @color/colorAccent 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18sp 4 | 16sp 5 | 14sp 6 | 12sp 7 | 55dp 8 | 15dp 9 | 10 | 64dp 11 | 16dp 12 | 15dp 13 | 10dp 14 | 16dp 15 | 20dp 16 | 17 | 20dp 18 | 10dp 19 | 250dp 20 | 15dp 21 | 5dp 22 | 20dp 23 | 18dp 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #448AFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 14 | 15 | 19 | 20 | 29 | 30 | 35 | 36 | 42 | 43 | 51 | 52 | 56 | 57 | 63 | 64 | 68 | 69 | 79 | 80 | -------------------------------------------------------------------------------- /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 | repositories { 5 | google() 6 | mavenCentral() 7 | maven { 8 | url "https://jitpack.io" 9 | } 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.1.0' 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 | mavenCentral() 22 | maven { 23 | url "https://jitpack.io" 24 | } 25 | } 26 | gradle.projectsEvaluated { 27 | tasks.withType(JavaCompile) { 28 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 29 | } 30 | } 31 | } 32 | 33 | task clean(type: Delete) { 34 | delete rootProject.buildDir 35 | } 36 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Sat Jul 03 11:05:27 CST 2021 14 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 15 | android.enableJetifier=true 16 | android.useAndroidX=true 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trojan-gfw/igniter/e0964bc5f4cd8c0c481405a1a38e9e493791c002/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-7.2-bin.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 | MSYS* | 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 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /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 execute 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 execute 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 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /pull_golibs.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import requests 4 | from lxml import etree 5 | 6 | 7 | def figure_out_download_url(): 8 | html = requests.get('https://github.com/trojan-gfw/igniter-go-libs/releases/') 9 | if not html.ok: 10 | return None 11 | root = etree.HTML(html.content) 12 | first_release_entry_div = root.find('.//div[@class="release-entry"]') 13 | if first_release_entry_div is None: 14 | return None 15 | download_a = first_release_entry_div.find('.//div/div[2]/details/div/div/div[1]/a') 16 | url_sub_path = download_a.get('href') 17 | return 'https://github.com' + url_sub_path 18 | 19 | 20 | def replace_golibs(): 21 | golibs_path = './app/src/libs/golibs.aar' 22 | if os.path.exists(golibs_path): 23 | os.remove(golibs_path) 24 | os.rename('tmp.aar', golibs_path) 25 | 26 | 27 | def download_golibs(): 28 | print('Figuring out url to download golibs.aar ...') 29 | download_url = figure_out_download_url() 30 | if download_url is None: 31 | print('Failed to figure out download url') 32 | return 33 | print('Download url: {0} downloading ...'.format(download_url)) 34 | r = requests.get(url=download_url, allow_redirects=True) 35 | if not r.ok: 36 | print('Download golibs.aar failed') 37 | return 38 | with open('tmp.aar', 'wb') as output: 39 | output.write(r.content) 40 | print('Download success') 41 | replace_golibs() 42 | print('Done') 43 | 44 | 45 | def main(): 46 | download_golibs() 47 | 48 | 49 | if __name__ == '__main__': 50 | main() 51 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------