├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── fabric.properties ├── proguard-rules.pro ├── schemas │ ├── com.github.cgg.clasha.data.LogsDatabase │ │ ├── 1.json │ │ ├── 2.json │ │ └── 3.json │ ├── com.github.cgg.clasha.data.PrivateDatabase │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ └── 4.json │ └── com.github.shadowsocks.database.PublicDatabase │ │ └── 1.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── cgg │ │ └── clasha │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── aidl │ │ └── com │ │ │ └── github │ │ │ └── cgg │ │ │ └── clasha │ │ │ └── aidl │ │ │ ├── IClashAService.aidl │ │ │ ├── IClashAServiceCallback.aidl │ │ │ └── TrafficStats.aidl │ ├── assets │ │ ├── Country.mmdb │ │ ├── clash │ │ │ ├── 7.5ab3a74114a4ce051b05.js │ │ │ ├── 7.5ab3a74114a4ce051b05.js.LICENSE │ │ │ ├── CNAME │ │ │ ├── app.930ecad2d62c944e72bd.css │ │ │ ├── app.cd83502eb9fe85a5c59b.js │ │ │ ├── app.cd83502eb9fe85a5c59b.js.LICENSE │ │ │ ├── core-js~app.d0ad192734a12bf5aec9.js │ │ │ ├── index.html │ │ │ ├── proxies.54d9dc6178af6177acdd.js │ │ │ ├── proxies.b5e30dff13c011457565.css │ │ │ ├── react~app.6b67113954659e454015.js │ │ │ ├── react~app.6b67113954659e454015.js.LICENSE │ │ │ ├── report.html │ │ │ ├── rules.0860925fbd80318b7939.js │ │ │ ├── rules.f60a65ba29535b87ba96.css │ │ │ ├── runtime.5dec3cfa22062be75ea8.js │ │ │ ├── vendors~chartjs.447e3b00d64ad017b55d.js │ │ │ ├── vendors~chartjs.447e3b00d64ad017b55d.js.LICENSE │ │ │ ├── yacd-128.png │ │ │ ├── yacd-64.png │ │ │ └── yacd.ico │ │ └── overture │ │ │ ├── china_ip_list.txt │ │ │ └── hosts │ ├── clash │ │ ├── clean.bash │ │ └── make.bash │ ├── ic_clasha_launcher-web.png │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── cgg │ │ │ └── clasha │ │ │ ├── App.kt │ │ │ ├── GlobalSettingsFragment.kt │ │ │ ├── GlobalSettingsPreferenceFragment.kt │ │ │ ├── JniHelper.java │ │ │ ├── LogsFragment.kt │ │ │ ├── LogsViewModel.kt │ │ │ ├── MainActivity.kt │ │ │ ├── ProfileListFragment.kt │ │ │ ├── TestActivity.kt │ │ │ ├── ToolbarFragment.kt │ │ │ ├── VpnRequestActivity.kt │ │ │ ├── aidl │ │ │ ├── ClashAConnection.kt │ │ │ └── TrafficStats.kt │ │ │ ├── bg │ │ │ ├── BaseService.kt │ │ │ ├── Executable.kt │ │ │ ├── GuardedProcessPool.kt │ │ │ ├── LocalDnsService.kt │ │ │ ├── LocalSocketListener.kt │ │ │ ├── ProxyInstance.kt │ │ │ ├── ProxyService.kt │ │ │ ├── ServiceNotification.kt │ │ │ ├── TileService.kt │ │ │ ├── TrafficMonitor.kt │ │ │ └── VpnService.kt │ │ │ ├── data │ │ │ ├── ConfigManager.kt │ │ │ ├── DataStore.kt │ │ │ ├── DateConverters.kt │ │ │ ├── KeyValuePair.kt │ │ │ ├── LogMessage.kt │ │ │ ├── LogsDataSource.kt │ │ │ ├── LogsDatabase.kt │ │ │ ├── LogsLocalDataSource.kt │ │ │ ├── LogsRepository.kt │ │ │ ├── OnPreferenceDataStoreChangeListener.kt │ │ │ ├── PrivateDatabase.kt │ │ │ ├── ProfileConfig.kt │ │ │ ├── RoomPreferenceDataStore.kt │ │ │ └── db │ │ │ │ └── PublicDatabase.kt │ │ │ ├── net │ │ │ ├── ConcurrentLocalSocketListener.kt │ │ │ ├── DefaultNetworkListener.kt │ │ │ ├── HTTPConfig.kt │ │ │ └── PageController.kt │ │ │ ├── utils │ │ │ ├── AppExecutors.kt │ │ │ ├── ArrayIterator.kt │ │ │ ├── Commandline.kt │ │ │ ├── DeviceStorageApp.kt │ │ │ ├── DialogHelper.kt │ │ │ ├── DiskIOThreadExecutor.kt │ │ │ ├── FileUtils.kt │ │ │ ├── Key.kt │ │ │ ├── Utils.kt │ │ │ ├── YamlFileType.kt │ │ │ └── YmlFileType.kt │ │ │ └── widget │ │ │ ├── ClashABottomSheetDialog.java │ │ │ ├── ClashABottomSheetWebview.java │ │ │ ├── ClashAWebviewBottomSheetDialog.java │ │ │ ├── EditTextDialog.kt │ │ │ └── ServiceButton.kt │ ├── jni │ │ ├── Android.mk │ │ ├── Application.mk │ │ ├── build-shared-executable.mk │ │ └── jni-helper.cpp │ └── res │ │ ├── color │ │ ├── background_service.xml │ │ └── nav_item_tint.xml │ │ ├── drawable-hdpi │ │ └── ic_config_yml.png │ │ ├── drawable-mdpi │ │ └── ic_config_yml.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ └── ic_config_yml.png │ │ ├── drawable-xxhdpi │ │ └── ic_config_yml.png │ │ ├── drawable-xxxhdpi │ │ └── ic_config_yml.png │ │ ├── drawable │ │ ├── background_profile.xml │ │ ├── bg_log_num.xml │ │ ├── bg_toolbar_radius.xml │ │ ├── ic_action_description.xml │ │ ├── ic_action_dns.xml │ │ ├── ic_action_logs.xml │ │ ├── ic_action_settings.xml │ │ ├── ic_arrow_back_24dp.xml │ │ ├── ic_clash_logo.xml │ │ ├── ic_close_24dp.xml │ │ ├── ic_device_data_usage.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_menu_camera.xml │ │ ├── ic_menu_gallery.xml │ │ ├── ic_menu_manage.xml │ │ ├── ic_menu_send.xml │ │ ├── ic_menu_share.xml │ │ ├── ic_menu_slideshow.xml │ │ ├── ic_navigation_close.xml │ │ ├── ic_navigation_menu.xml │ │ ├── ic_notifications.xml │ │ ├── ic_refresh_update.xml │ │ ├── ic_service_active.xml │ │ ├── ic_service_busy.xml │ │ ├── ic_service_connected.xml │ │ ├── ic_service_connecting.xml │ │ ├── ic_service_idle.xml │ │ ├── ic_service_stopped.xml │ │ ├── ic_service_stopping.xml │ │ ├── progress_horizontal_wv.xml │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── activity_test.xml │ │ ├── app_bar_test.xml │ │ ├── content_main.xml │ │ ├── content_test.xml │ │ ├── dialog_download_config.xml │ │ ├── dialog_edit_text.xml │ │ ├── echat_design_bottom_sheet_dialog.xml │ │ ├── echat_dialog_layout_webview.xml │ │ ├── layout_fr_main.xml │ │ ├── layout_global_settings.xml │ │ ├── layout_logs_view.xml │ │ ├── layout_main.xml │ │ ├── list_log.xml │ │ ├── list_profile.xml │ │ ├── nav_header_test.xml │ │ └── navigation_header.xml │ │ ├── menu │ │ ├── activity_test_drawer.xml │ │ ├── logs_menu.xml │ │ ├── main_menu.xml │ │ ├── navigation_main.xml │ │ └── test.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_clasha_launcher.xml │ │ ├── ic_clasha_launcher_round.xml │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_clasha_launcher.png │ │ ├── ic_clasha_launcher_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_clasha_launcher.png │ │ ├── ic_clasha_launcher_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_clasha_launcher.png │ │ ├── ic_clasha_launcher_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_clasha_launcher.png │ │ ├── ic_clasha_launcher_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_clasha_launcher.png │ │ ├── ic_clasha_launcher_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── network_security_config.xml │ │ └── pref_global.xml │ └── test │ └── java │ └── com │ └── github │ └── cgg │ └── clasha │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | 13 | # Gradle & Android Studio 14 | local.properties 15 | .gradle/ 16 | .externalNativeBuild/ 17 | build/ 18 | captures/ 19 | release/ 20 | ccg 21 | 22 | .idea/ 23 | *.iml 24 | 25 | 26 | app/src/main/clash/.deps 27 | app/src/main/clash/bin 28 | 29 | core/src/overture/.deps 30 | core/src/overture/bin 31 | core/src/overture/pkg 32 | 33 | core/src/overture/src/github.com/Sirupsen 34 | core/src/overture/src/github.com/miekg 35 | core/src/overture/src/golang.org 36 | 37 | # work in progress 38 | tv/ 39 | 40 | # release apks 41 | *.apk 42 | 43 | 44 | .classpath 45 | .settings 46 | 47 | node_modules/ 48 | package-lock.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "app/src/main/jni/badvpn"] 2 | path = app/src/main/jni/badvpn 3 | url = https://github.com/shadowsocks/badvpn.git 4 | [submodule "app/src/main/jni/libancillary"] 5 | path = app/src/main/jni/libancillary 6 | url = https://github.com/shadowsocks/libancillary.git 7 | [submodule "app/src/main/clash/src/clash"] 8 | path = app/src/main/clash/src/clash 9 | url = git@github.com:Dreamacro/clash.git 10 | branch = dev 11 | [submodule "app/src/main/clash/src/github.com/shadowsocks/overture"] 12 | path = app/src/main/clash/src/github.com/shadowsocks/overture 13 | url = https://github.com/shadowsocks/overture.git 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Clash 3 |
4 | ClashA 5 |
6 |

7 | 8 |

An Android GUI for Clash.

9 | 10 | ### ClashA will be deprecated soon. For a Clash GUI on Android in active development, use **Clash for Android** developed by Kr328 instead, [Telegram Channel](https://t.me/clash_for_android_channel). 11 | 12 | ClashA final version: 0.0.3+ 13 | 14 | ### Keywords and Useful Links 15 | - Clash : A multi-platform & rule-base tunnel, [Github](https://github.com/Dreamacro/clash) 16 | - ClashX for Mac : A GUI of Clash on macOS, [Github](https://github.com/yichengchen/clashX) 17 | - Clash for Windows : a GUI of Clash on Windows, [Github](https://raw.githubusercontent.com/Fndroid/clash_for_windows_pkg) 18 | 19 | 20 | ### Build 21 | 22 | #### Build Dependencies 23 | 24 | * JDK 1.8 25 | * Android SDK 26 | - Android NDK 27 | #### Steps 28 | * Clone the repo using `git clone --recurse-submodules ` or update submodules using `git submodule update --init --recursive` 29 | * Build it using Android Studio or gradle script 30 | 31 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/fabric.properties: -------------------------------------------------------------------------------- 1 | #Contains API Secret used to validate your application. Commit to internal source control; avoid making secret public. 2 | #Fri Dec 28 19:03:16 CST 2018 3 | apiSecret=a3234883e839712738aefd47eca3ee1f15743707e06e435e6259a8a184d83040 4 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/schemas/com.github.cgg.clasha.data.LogsDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "800adc6f0950081b813ea592d0eec52e", 6 | "entities": [ 7 | { 8 | "tableName": "LogMessage", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `src` TEXT, `dst` TEXT, `matchType` TEXT, `GroupName` TEXT, `logType` TEXT, `time` INTEGER NOT NULL, `currentRunDate` INTEGER NOT NULL, `profileId` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "src", 19 | "columnName": "src", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "dst", 25 | "columnName": "dst", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "matchType", 31 | "columnName": "matchType", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "GroupName", 37 | "columnName": "GroupName", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | }, 41 | { 42 | "fieldPath": "logType", 43 | "columnName": "logType", 44 | "affinity": "TEXT", 45 | "notNull": false 46 | }, 47 | { 48 | "fieldPath": "time", 49 | "columnName": "time", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | }, 53 | { 54 | "fieldPath": "currentRunDate", 55 | "columnName": "currentRunDate", 56 | "affinity": "INTEGER", 57 | "notNull": true 58 | }, 59 | { 60 | "fieldPath": "profileId", 61 | "columnName": "profileId", 62 | "affinity": "INTEGER", 63 | "notNull": true 64 | } 65 | ], 66 | "primaryKey": { 67 | "columnNames": [ 68 | "id" 69 | ], 70 | "autoGenerate": true 71 | }, 72 | "indices": [], 73 | "foreignKeys": [] 74 | } 75 | ], 76 | "setupQueries": [ 77 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 78 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"800adc6f0950081b813ea592d0eec52e\")" 79 | ] 80 | } 81 | } -------------------------------------------------------------------------------- /app/schemas/com.github.cgg.clasha.data.LogsDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "7bb874dec5f9828eae1791559576ab8a", 6 | "entities": [ 7 | { 8 | "tableName": "LogMessage", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT, `originContent` TEXT, `logType` TEXT, `time` INTEGER NOT NULL, `currentRunDate` INTEGER NOT NULL, `profileId` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "content", 19 | "columnName": "content", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "originContent", 25 | "columnName": "originContent", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "logType", 31 | "columnName": "logType", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "time", 37 | "columnName": "time", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | }, 41 | { 42 | "fieldPath": "currentRunDate", 43 | "columnName": "currentRunDate", 44 | "affinity": "INTEGER", 45 | "notNull": true 46 | }, 47 | { 48 | "fieldPath": "profileId", 49 | "columnName": "profileId", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | } 53 | ], 54 | "primaryKey": { 55 | "columnNames": [ 56 | "id" 57 | ], 58 | "autoGenerate": true 59 | }, 60 | "indices": [], 61 | "foreignKeys": [] 62 | } 63 | ], 64 | "setupQueries": [ 65 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 66 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7bb874dec5f9828eae1791559576ab8a\")" 67 | ] 68 | } 69 | } -------------------------------------------------------------------------------- /app/schemas/com.github.cgg.clasha.data.LogsDatabase/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 3, 5 | "identityHash": "7bb874dec5f9828eae1791559576ab8a", 6 | "entities": [ 7 | { 8 | "tableName": "LogMessage", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT, `originContent` TEXT, `logType` TEXT, `time` INTEGER NOT NULL, `currentRunDate` INTEGER NOT NULL, `profileId` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "content", 19 | "columnName": "content", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "originContent", 25 | "columnName": "originContent", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "logType", 31 | "columnName": "logType", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "time", 37 | "columnName": "time", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | }, 41 | { 42 | "fieldPath": "currentRunDate", 43 | "columnName": "currentRunDate", 44 | "affinity": "INTEGER", 45 | "notNull": true 46 | }, 47 | { 48 | "fieldPath": "profileId", 49 | "columnName": "profileId", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | } 53 | ], 54 | "primaryKey": { 55 | "columnNames": [ 56 | "id" 57 | ], 58 | "autoGenerate": true 59 | }, 60 | "indices": [], 61 | "foreignKeys": [] 62 | } 63 | ], 64 | "setupQueries": [ 65 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 66 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7bb874dec5f9828eae1791559576ab8a\")" 67 | ] 68 | } 69 | } -------------------------------------------------------------------------------- /app/schemas/com.github.cgg.clasha.data.PrivateDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "0874651dad55d4b637ccfe8be0f9278f", 6 | "entities": [ 7 | { 8 | "tableName": "ProfileConfig", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `configName` TEXT, `url` TEXT, `proxyItems` TEXT, `proxyGroupItems` TEXT, `ruleItems` TEXT, `order` INTEGER NOT NULL, `origin` TEXT, `itemType` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "configName", 19 | "columnName": "configName", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "url", 25 | "columnName": "url", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "proxyItems", 31 | "columnName": "proxyItems", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "proxyGroupItems", 37 | "columnName": "proxyGroupItems", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | }, 41 | { 42 | "fieldPath": "ruleItems", 43 | "columnName": "ruleItems", 44 | "affinity": "TEXT", 45 | "notNull": false 46 | }, 47 | { 48 | "fieldPath": "order", 49 | "columnName": "order", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | }, 53 | { 54 | "fieldPath": "origin", 55 | "columnName": "origin", 56 | "affinity": "TEXT", 57 | "notNull": false 58 | }, 59 | { 60 | "fieldPath": "itemType", 61 | "columnName": "itemType", 62 | "affinity": "INTEGER", 63 | "notNull": true 64 | } 65 | ], 66 | "primaryKey": { 67 | "columnNames": [ 68 | "id" 69 | ], 70 | "autoGenerate": true 71 | }, 72 | "indices": [], 73 | "foreignKeys": [] 74 | } 75 | ], 76 | "setupQueries": [ 77 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 78 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"0874651dad55d4b637ccfe8be0f9278f\")" 79 | ] 80 | } 81 | } -------------------------------------------------------------------------------- /app/schemas/com.github.cgg.clasha.data.PrivateDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "0874651dad55d4b637ccfe8be0f9278f", 6 | "entities": [ 7 | { 8 | "tableName": "ProfileConfig", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `configName` TEXT, `url` TEXT, `proxyItems` TEXT, `proxyGroupItems` TEXT, `ruleItems` TEXT, `order` INTEGER NOT NULL, `origin` TEXT, `itemType` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "configName", 19 | "columnName": "configName", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "url", 25 | "columnName": "url", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "proxyItems", 31 | "columnName": "proxyItems", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "proxyGroupItems", 37 | "columnName": "proxyGroupItems", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | }, 41 | { 42 | "fieldPath": "ruleItems", 43 | "columnName": "ruleItems", 44 | "affinity": "TEXT", 45 | "notNull": false 46 | }, 47 | { 48 | "fieldPath": "order", 49 | "columnName": "order", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | }, 53 | { 54 | "fieldPath": "origin", 55 | "columnName": "origin", 56 | "affinity": "TEXT", 57 | "notNull": false 58 | }, 59 | { 60 | "fieldPath": "itemType", 61 | "columnName": "itemType", 62 | "affinity": "INTEGER", 63 | "notNull": true 64 | } 65 | ], 66 | "primaryKey": { 67 | "columnNames": [ 68 | "id" 69 | ], 70 | "autoGenerate": true 71 | }, 72 | "indices": [], 73 | "foreignKeys": [] 74 | } 75 | ], 76 | "setupQueries": [ 77 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 78 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"0874651dad55d4b637ccfe8be0f9278f\")" 79 | ] 80 | } 81 | } -------------------------------------------------------------------------------- /app/schemas/com.github.cgg.clasha.data.PrivateDatabase/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 3, 5 | "identityHash": "c2f1b166f21150346bd1c35d155e6c3c", 6 | "entities": [ 7 | { 8 | "tableName": "ProfileConfig", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `configName` TEXT, `url` TEXT, `proxyItems` TEXT, `proxyGroupItems` TEXT, `ruleItems` TEXT, `order` INTEGER NOT NULL, `origin` TEXT, `itemType` INTEGER NOT NULL, `time` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "configName", 19 | "columnName": "configName", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "url", 25 | "columnName": "url", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "proxyItems", 31 | "columnName": "proxyItems", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "proxyGroupItems", 37 | "columnName": "proxyGroupItems", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | }, 41 | { 42 | "fieldPath": "ruleItems", 43 | "columnName": "ruleItems", 44 | "affinity": "TEXT", 45 | "notNull": false 46 | }, 47 | { 48 | "fieldPath": "order", 49 | "columnName": "order", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | }, 53 | { 54 | "fieldPath": "origin", 55 | "columnName": "origin", 56 | "affinity": "TEXT", 57 | "notNull": false 58 | }, 59 | { 60 | "fieldPath": "itemType", 61 | "columnName": "itemType", 62 | "affinity": "INTEGER", 63 | "notNull": true 64 | }, 65 | { 66 | "fieldPath": "time", 67 | "columnName": "time", 68 | "affinity": "INTEGER", 69 | "notNull": true 70 | } 71 | ], 72 | "primaryKey": { 73 | "columnNames": [ 74 | "id" 75 | ], 76 | "autoGenerate": true 77 | }, 78 | "indices": [], 79 | "foreignKeys": [] 80 | } 81 | ], 82 | "setupQueries": [ 83 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 84 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"c2f1b166f21150346bd1c35d155e6c3c\")" 85 | ] 86 | } 87 | } -------------------------------------------------------------------------------- /app/schemas/com.github.cgg.clasha.data.PrivateDatabase/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 4, 5 | "identityHash": "4de05882a307afa58a0a079fd0d9b0a5", 6 | "entities": [ 7 | { 8 | "tableName": "ProfileConfig", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `configName` TEXT, `url` TEXT, `proxyItems` TEXT, `proxyGroupItems` TEXT, `ruleItems` TEXT, `order` INTEGER NOT NULL, `origin` TEXT, `itemType` INTEGER NOT NULL, `selector` TEXT, `time` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "configName", 19 | "columnName": "configName", 20 | "affinity": "TEXT", 21 | "notNull": false 22 | }, 23 | { 24 | "fieldPath": "url", 25 | "columnName": "url", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | }, 29 | { 30 | "fieldPath": "proxyItems", 31 | "columnName": "proxyItems", 32 | "affinity": "TEXT", 33 | "notNull": false 34 | }, 35 | { 36 | "fieldPath": "proxyGroupItems", 37 | "columnName": "proxyGroupItems", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | }, 41 | { 42 | "fieldPath": "ruleItems", 43 | "columnName": "ruleItems", 44 | "affinity": "TEXT", 45 | "notNull": false 46 | }, 47 | { 48 | "fieldPath": "order", 49 | "columnName": "order", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | }, 53 | { 54 | "fieldPath": "origin", 55 | "columnName": "origin", 56 | "affinity": "TEXT", 57 | "notNull": false 58 | }, 59 | { 60 | "fieldPath": "itemType", 61 | "columnName": "itemType", 62 | "affinity": "INTEGER", 63 | "notNull": true 64 | }, 65 | { 66 | "fieldPath": "selector", 67 | "columnName": "selector", 68 | "affinity": "TEXT", 69 | "notNull": false 70 | }, 71 | { 72 | "fieldPath": "time", 73 | "columnName": "time", 74 | "affinity": "INTEGER", 75 | "notNull": true 76 | } 77 | ], 78 | "primaryKey": { 79 | "columnNames": [ 80 | "id" 81 | ], 82 | "autoGenerate": true 83 | }, 84 | "indices": [], 85 | "foreignKeys": [] 86 | } 87 | ], 88 | "setupQueries": [ 89 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 90 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"4de05882a307afa58a0a079fd0d9b0a5\")" 91 | ] 92 | } 93 | } -------------------------------------------------------------------------------- /app/schemas/com.github.shadowsocks.database.PublicDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "f1aab1fb633378621635c344dbc8ac7b", 6 | "entities": [ 7 | { 8 | "tableName": "KeyValuePair", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "key", 13 | "columnName": "key", 14 | "affinity": "TEXT", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "valueType", 19 | "columnName": "valueType", 20 | "affinity": "INTEGER", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "value", 25 | "columnName": "value", 26 | "affinity": "BLOB", 27 | "notNull": true 28 | } 29 | ], 30 | "primaryKey": { 31 | "columnNames": [ 32 | "key" 33 | ], 34 | "autoGenerate": false 35 | }, 36 | "indices": [], 37 | "foreignKeys": [] 38 | } 39 | ], 40 | "setupQueries": [ 41 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 42 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"f1aab1fb633378621635c344dbc8ac7b\")" 43 | ] 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/cgg/clasha/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.github.clasha.clasha", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 20 | 23 | 24 | 34 | 35 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 65 | 66 | 67 | 68 | 69 | 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/aidl/com/github/cgg/clasha/aidl/IClashAService.aidl: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.aidl; 2 | 3 | import com.github.cgg.clasha.aidl.IClashAServiceCallback; 4 | 5 | interface IClashAService { 6 | int getState(); 7 | String getProfileName(); 8 | 9 | void registerCallback(in IClashAServiceCallback cb); 10 | void startListeningForBandwidth(in IClashAServiceCallback cb, long timeout); 11 | oneway void stopListeningForBandwidth(in IClashAServiceCallback cb); 12 | oneway void unregisterCallback(in IClashAServiceCallback cb); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/aidl/com/github/cgg/clasha/aidl/IClashAServiceCallback.aidl: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.aidl; 2 | 3 | import com.github.cgg.clasha.aidl.TrafficStats; 4 | 5 | oneway interface IClashAServiceCallback { 6 | void stateChanged(int state, String profileName, String msg); 7 | void trafficUpdated(long profileId, in TrafficStats stats); 8 | // Traffic data has persisted to database, listener should refetch their data from database 9 | void trafficPersisted(long profileId); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/aidl/com/github/cgg/clasha/aidl/TrafficStats.aidl: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.aidl; 2 | 3 | parcelable TrafficStats; 4 | -------------------------------------------------------------------------------- /app/src/main/assets/Country.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccg2018/ClashA/3207923a14dd15982dc5c1f97e819a11d573b972/app/src/main/assets/Country.mmdb -------------------------------------------------------------------------------- /app/src/main/assets/clash/7.5ab3a74114a4ce051b05.js.LICENSE: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) Microsoft Corporation. All rights reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | this file except in compliance with the License. You may obtain a copy of the 5 | License at http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 8 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 9 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 10 | MERCHANTABLITY OR NON-INFRINGEMENT. 11 | 12 | See the Apache Version 2.0 License for specific language governing permissions 13 | and limitations under the License. 14 | ***************************************************************************** */ 15 | -------------------------------------------------------------------------------- /app/src/main/assets/clash/CNAME: -------------------------------------------------------------------------------- 1 | yacd.haishan.me 2 | -------------------------------------------------------------------------------- /app/src/main/assets/clash/app.cd83502eb9fe85a5c59b.js.LICENSE: -------------------------------------------------------------------------------- 1 | /** @license React v0.0.0-experimental-b53ea6ca0 2 | * scheduler.production.min.js 3 | * 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | 10 | /*! 11 | Copyright (c) 2015 Jed Watson. 12 | Based on code that is Copyright 2013-2015, Facebook, Inc. 13 | All rights reserved. 14 | */ 15 | 16 | /* 17 | object-assign 18 | (c) Sindre Sorhus 19 | @license MIT 20 | */ 21 | 22 | /*! 23 | Copyright (c) 2017 Jed Watson. 24 | Licensed under the MIT License (MIT), see 25 | http://jedwatson.github.io/classnames 26 | */ 27 | 28 | /** @license React v16.6.1 29 | * react-cache.production.min.js 30 | * 31 | * Copyright (c) Facebook, Inc. and its affiliates. 32 | * 33 | * This source code is licensed under the MIT license found in the 34 | * LICENSE file in the root directory of this source tree. 35 | */ 36 | 37 | /*! 38 | * Adapted from jQuery UI core 39 | * 40 | * http://jqueryui.com 41 | * 42 | * Copyright 2014 jQuery Foundation and other contributors 43 | * Released under the MIT license. 44 | * http://jquery.org/license 45 | * 46 | * http://api.jqueryui.com/category/ui-core/ 47 | */ 48 | 49 | /** @license React v16.11.0 50 | * react-is.production.min.js 51 | * 52 | * Copyright (c) Facebook, Inc. and its affiliates. 53 | * 54 | * This source code is licensed under the MIT license found in the 55 | * LICENSE file in the root directory of this source tree. 56 | */ 57 | -------------------------------------------------------------------------------- /app/src/main/assets/clash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | yacd - Yet Another Clash Dashboard 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/assets/clash/proxies.b5e30dff13c011457565.css: -------------------------------------------------------------------------------- 1 | ._2V-RqIAl7n{border-radius:20px;padding:3px 0;color:#eee;font-size:.6em}@media screen and (min-width:30em){._2V-RqIAl7n{font-size:1em}} 2 | .NpfXwxWAxo{position:relative;padding:5px;border-radius:8px;background-color:var(--color-bg-proxy-selected)}@media screen and (min-width:30em){.NpfXwxWAxo{border-radius:10px;padding:10px}}.NpfXwxWAxo._2zD7drviYH{background-color:var(--color-focus-blue);color:#ddd}.NpfXwxWAxo._2bylJNYYdN{opacity:.5}._1rVl-Kdmss{font-family:var(--font-mono);font-size:.6em}@media screen and (min-width:30em){._1rVl-Kdmss{font-size:1em}}._3kdi5nima5{width:100%;overflow:hidden;text-overflow:ellipsis;margin-bottom:5px;font-size:.85em}@media screen and (min-width:30em){._3kdi5nima5{font-size:1.1em}}._12JM32OJa5{height:30px;display:-webkit-box;display:flex;-webkit-box-align:end;align-items:flex-end} 3 | ._3PCSxT0l14>h2{margin-top:0;font-size:1.3em}@media screen and (min-width:30em){._3PCSxT0l14>h2{font-size:1.5em}}._3PCSxT0l14>h2 span:nth-child(2){font-size:12px;color:#777;font-weight:400;margin:0 .3em}._1yYRIyvlRd{display:-webkit-box;display:flex;flex-wrap:wrap}._1OcDlvlM5R{max-width:280px;margin:2px;-webkit-transition:-webkit-transform .2s ease-in-out;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out}@media screen and (min-width:30em){._1OcDlvlM5R{min-width:150px;margin:10px}}._1OcDlvlM5R._3oAxPKtZFv{cursor:pointer}._1OcDlvlM5R._3oAxPKtZFv:hover{-webkit-transform:translateY(-2px);transform:translateY(-2px)} 4 | .pWc1mov26e{padding-bottom:50px}._1myfcMimT9{padding:10px 15px}@media screen and (min-width:30em){._1myfcMimT9{padding:10px 40px}} 5 | -------------------------------------------------------------------------------- /app/src/main/assets/clash/react~app.6b67113954659e454015.js.LICENSE: -------------------------------------------------------------------------------- 1 | /** @license React v0.16.2 2 | * scheduler.production.min.js 3 | * 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | 10 | /** @license React v16.10.2 11 | * react-dom.production.min.js 12 | * 13 | * Copyright (c) Facebook, Inc. and its affiliates. 14 | * 15 | * This source code is licensed under the MIT license found in the 16 | * LICENSE file in the root directory of this source tree. 17 | */ 18 | 19 | /** @license React v0.0.0-experimental-b53ea6ca0 20 | * react.production.min.js 21 | * 22 | * Copyright (c) Facebook, Inc. and its affiliates. 23 | * 24 | * This source code is licensed under the MIT license found in the 25 | * LICENSE file in the root directory of this source tree. 26 | */ 27 | -------------------------------------------------------------------------------- /app/src/main/assets/clash/rules.0860925fbd80318b7939.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[4],{BVyM:function(e,t,r){"use strict";r.r(t);var a=r("ODXe"),n=r("q1tI"),c=r.n(n),l=r("u4Dv"),o=r("5Wrh"),i=r("iR1w"),u=r("17x9"),s=r.n(u);function f(){return(f=Object.assign||function(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var c=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var m=function(e){var t=e.color,r=e.size,a=p(e,["color","size"]);return c.a.createElement("svg",f({xmlns:"http://www.w3.org/2000/svg",width:r,height:r,viewBox:"0 0 24 24",fill:"none",stroke:t,strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},a),c.a.createElement("polyline",{points:"23 4 23 10 17 10"}),c.a.createElement("path",{d:"M20.49 15a9 9 0 1 1-2.12-9.36L23 10"}))};m.propTypes={color:s.a.string,size:s.a.oneOfType([s.a.string,s.a.number])},m.defaultProps={color:"currentColor",size:"24"};var d=m,v=r("DKqX"),y=r("xrux"),b=r.n(y),h={_default:"#59caf9",DIRECT:"#f5bc41",REJECT:"#cb3166"};function E(e){var t=e.type,r=e.payload,a=e.proxy,n=e.id,l=function(e){var t=e.proxy,r=h._default;return h[t]&&(r=h[t]),{color:r}}({proxy:a});return c.a.createElement("div",{className:b.a.rule},c.a.createElement("div",{className:b.a.left},n),c.a.createElement("div",null,c.a.createElement("div",{className:b.a.b},r),c.a.createElement("div",{className:b.a.a},c.a.createElement("div",{className:b.a.type},t),c.a.createElement("div",{style:l},a))))}E.propTypes={id:s.a.number,type:s.a.string,payload:s.a.string,proxy:s.a.string};var O=E,g=r("II4a"),w=r("Tvb5"),x={updateSearchText:w.f},j=Object(g.a)({mapStateToProps:function(e){return{searchText:Object(w.e)(e)}},actions:x}),k=r("Kv4h");r.d(t,"default",(function(){return D}));var N=c.a.memo,R=c.a.useEffect,T=c.a.useMemo,I=30,S=function(e){return{rules:Object(w.d)(e)}},_={fetchRules:w.b,fetchRulesOnce:w.c};function z(e,t){return t[e].id}var C=N((function(e){var t=e.index,r=e.style,a=e.data[t];return c.a.createElement("div",{style:r},c.a.createElement(O,a))}),i.b);function D(){var e=Object(l.b)(_),t=e.fetchRulesOnce,r=e.fetchRules,n=Object(l.c)(S).rules;R((function(){t()}),[t]);var u=Object(k.a)(),s=Object(a.a)(u,2),f=s[0],p=s[1],m=T((function(){return c.a.createElement(d,{width:16})}),[]);return c.a.createElement("div",null,c.a.createElement(v.a,{title:"Rules"}),c.a.createElement(j,null),c.a.createElement("div",{ref:f,style:{paddingBottom:I}},c.a.createElement(i.a,{height:p-I,width:"100%",itemCount:n.length,itemSize:80,itemData:n,itemKey:z},C)),c.a.createElement("div",{className:"fabgrp"},c.a.createElement(o.a,{text:"Refresh",icon:m,onClick:r})))}},xrux:function(e,t,r){e.exports={rule:"_3eSLieOhVX",left:"_2n1pW09UvV",a:"t1XJIwvW7A",b:"_1fNf8kj0HA",type:"_3yJmN0tON0"}}}]); -------------------------------------------------------------------------------- /app/src/main/assets/clash/rules.f60a65ba29535b87ba96.css: -------------------------------------------------------------------------------- 1 | ._3eSLieOhVX{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center;padding:6px 15px}@media screen and (min-width:30em){._3eSLieOhVX{padding:10px 40px}}._2n1pW09UvV{width:40px;padding-right:15px;color:var(--color-text-secondary);opacity:.4}.t1XJIwvW7A{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center;font-size:12px;opacity:.8}._1fNf8kj0HA{padding:10px 0;font-family:Roboto Mono,Menlo,monospace;font-size:16px}@media screen and (min-width:30em){._1fNf8kj0HA{font-size:19px}}._3yJmN0tON0{width:110px} 2 | -------------------------------------------------------------------------------- /app/src/main/assets/clash/runtime.5dec3cfa22062be75ea8.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(t){for(var n,o,i=t[0],l=t[1],f=t[2],s=t[3]||[],d=0,h=[];d 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * 18 | * ___====-_ _-====___ 19 | * _--^^^#####// \\#####^^^--_ 20 | * _-^##########// ( ) \\##########^-_ 21 | * -############// |\^^/| \\############- 22 | * _/############// (@::@) \\############\_ 23 | * /#############(( \\// ))#############\ 24 | * -###############\\ (oo) //###############- 25 | * -#################\\ / VV \ //#################- 26 | * -###################\\/ \//###################- 27 | * _#/|##########/\######( /\ )######/\##########|\#_ 28 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 29 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 30 | * ` ` ` ` / | | | | \ ' ' ' ' 31 | * ( | | | | ) 32 | * __\ | | | | /__ 33 | * (vvv(VVV)(VVV)vvv) 34 | * 35 | * HERE BE DRAGONS 36 | * 37 | */ 38 | 39 | package com.github.cgg.clasha; 40 | 41 | import android.os.Build; 42 | import android.system.ErrnoException; 43 | import androidx.annotation.NonNull; 44 | import androidx.annotation.Nullable; 45 | 46 | public class JniHelper { 47 | static { 48 | System.loadLibrary("jni-helper"); 49 | } 50 | 51 | @Deprecated // Use Process.destroy() since API 24 52 | public static void sigtermCompat(@NonNull Process process) throws Exception { 53 | if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); 54 | int errno = sigterm(process); 55 | if (errno != 0) throw new ErrnoException("kill", errno); 56 | } 57 | 58 | @Deprecated // only implemented for before API 24 59 | public static boolean waitForCompat(@NonNull Process process, long millis) throws Exception { 60 | if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); 61 | final Object mutex = getExitValueMutex(process); 62 | synchronized (mutex) { 63 | if (getExitValue(process) == null) mutex.wait(millis); 64 | return getExitValue(process) != null; 65 | } 66 | } 67 | 68 | public static native int sigkill(int pid); 69 | private static native int sigterm(Process process); 70 | private static native Integer getExitValue(Process process); 71 | private static native Object getExitValueMutex(Process process); 72 | public static native int sendFd(int fd, @NonNull String path); 73 | public static native void close(int fd); 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/LogsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha 2 | 3 | import androidx.databinding.ObservableArrayList 4 | import androidx.databinding.ObservableField 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import com.github.cgg.clasha.data.LogMessage 8 | 9 | 10 | /** 11 | * @Author: ccg 12 | * @Email: ccgccg2019@gmail.com 13 | * @program: ClashA 14 | * @create: 2019-06-09 15 | * @describe 16 | */ 17 | class LogsViewModel : ViewModel() { 18 | val title = ObservableField() 19 | var data = MutableLiveData>() 20 | 21 | fun setData(list: List) { 22 | data.postValue(list) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/TestActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha 2 | 3 | import android.os.Bundle 4 | import com.google.android.material.snackbar.Snackbar 5 | import com.google.android.material.navigation.NavigationView 6 | import android.view.Menu 7 | import android.view.MenuItem 8 | import androidx.appcompat.app.ActionBarDrawerToggle 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.core.view.GravityCompat 11 | import kotlinx.android.synthetic.main.activity_test.* 12 | import kotlinx.android.synthetic.main.app_bar_test.* 13 | 14 | class TestActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_test) 19 | setSupportActionBar(toolbar) 20 | 21 | fab.setOnClickListener { view -> 22 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 23 | .setAction("Action", null).show() 24 | } 25 | 26 | val toggle = ActionBarDrawerToggle( 27 | this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close 28 | ) 29 | drawer_layout.addDrawerListener(toggle) 30 | toggle.syncState() 31 | 32 | nav_view.setNavigationItemSelectedListener(this) 33 | } 34 | 35 | override fun onBackPressed() { 36 | if (drawer_layout.isDrawerOpen(GravityCompat.START)) { 37 | drawer_layout.closeDrawer(GravityCompat.START) 38 | } else { 39 | super.onBackPressed() 40 | } 41 | } 42 | 43 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 44 | // Inflate the menu; this adds items to the action bar if it is present. 45 | menuInflater.inflate(R.menu.test, menu) 46 | return true 47 | } 48 | 49 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 50 | // Handle action bar item clicks here. The action bar will 51 | // automatically handle clicks on the Home/Up button, so long 52 | // as you specify a parent activity in AndroidManifest.xml. 53 | when (item.itemId) { 54 | R.id.action_settings -> return true 55 | else -> return super.onOptionsItemSelected(item) 56 | } 57 | } 58 | 59 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 60 | // Handle navigation view item clicks here. 61 | when (item.itemId) { 62 | R.id.nav_camera -> { 63 | // Handle the camera action 64 | } 65 | R.id.nav_gallery -> { 66 | 67 | } 68 | R.id.nav_slideshow -> { 69 | 70 | } 71 | R.id.nav_manage -> { 72 | 73 | } 74 | R.id.nav_share -> { 75 | 76 | } 77 | R.id.nav_send -> { 78 | 79 | } 80 | } 81 | 82 | drawer_layout.closeDrawer(GravityCompat.START) 83 | return true 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/ToolbarFragment.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.widget.Toolbar 6 | import androidx.core.view.GravityCompat 7 | import androidx.fragment.app.Fragment 8 | import com.github.cgg.clasha.aidl.TrafficStats 9 | 10 | /** 11 | * @Author: ccg 12 | * @Email: ccgccg2019@gmail.com 13 | * @program: ClashA 14 | * @create: 2019-03-14 15 | * @describe 16 | */ 17 | open class ToolbarFragment : Fragment() { 18 | lateinit var toolbar: Toolbar 19 | 20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 21 | super.onViewCreated(view, savedInstanceState) 22 | toolbar = view.findViewById(R.id.toolbar) 23 | toolbar.setNavigationIcon(R.drawable.ic_navigation_menu) 24 | toolbar.setNavigationOnClickListener { (activity as MainActivity).drawer.openDrawer(GravityCompat.START) } 25 | } 26 | 27 | open fun onTrafficUpdated(profileId: Long, stats: TrafficStats) {} 28 | 29 | open fun onBackPressed(): Boolean = false 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/VpnRequestActivity.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2017 by Max Lv * 4 | * Copyright (C) 2017 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.cgg.clasha 22 | 23 | import android.app.KeyguardManager 24 | import android.content.BroadcastReceiver 25 | import android.content.Intent 26 | import android.content.IntentFilter 27 | import android.net.VpnService 28 | import android.os.Bundle 29 | import android.util.Log 30 | import android.widget.Toast 31 | import androidx.appcompat.app.AppCompatActivity 32 | import androidx.core.content.getSystemService 33 | import com.blankj.utilcode.util.LogUtils 34 | import com.crashlytics.android.Crashlytics 35 | import com.github.cgg.clasha.App.Companion.app 36 | import com.github.cgg.clasha.aidl.ClashAConnection 37 | import com.github.cgg.clasha.aidl.IClashAService 38 | import com.github.cgg.clasha.bg.BaseService 39 | import com.github.cgg.clasha.data.DataStore 40 | import com.github.cgg.clasha.utils.Key 41 | import com.github.cgg.clasha.utils.broadcastReceiver 42 | 43 | class VpnRequestActivity : AppCompatActivity() { 44 | companion object { 45 | private const val TAG = "VpnRequestActivity" 46 | private const val REQUEST_CONNECT = 1 47 | } 48 | 49 | private var receiver: BroadcastReceiver? = null 50 | 51 | override fun onCreate(savedInstanceState: Bundle?) { 52 | super.onCreate(savedInstanceState) 53 | if (DataStore.serviceMode != Key.modeVpn) { 54 | finish() 55 | return 56 | } 57 | if (getSystemService()!!.isKeyguardLocked) { 58 | receiver = broadcastReceiver { _, _ -> request() } 59 | registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT)) 60 | } else request() 61 | } 62 | 63 | 64 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 65 | if (resultCode == RESULT_OK) app.startService() else { 66 | Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show() 67 | LogUtils.w(TAG, "Failed to start VpnService from onActivityResult: $data") 68 | Crashlytics.log(Log.WARN, TAG, "Failed to start VpnService from onActivityResult: $data") 69 | } 70 | finish() 71 | } 72 | 73 | private fun request() { 74 | val intent = VpnService.prepare(this) 75 | if (intent == null) onActivityResult(REQUEST_CONNECT, RESULT_OK, null) 76 | else startActivityForResult(intent, REQUEST_CONNECT) 77 | } 78 | 79 | override fun onDestroy() { 80 | super.onDestroy() 81 | if (receiver != null) unregisterReceiver(receiver) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/aidl/TrafficStats.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2019 by Max Lv * 4 | * Copyright (C) 2019 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.cgg.clasha.aidl 22 | 23 | import android.os.Parcel 24 | import android.os.Parcelable 25 | 26 | data class TrafficStats( 27 | // Bytes per second 28 | var txRate: Long = 0L, 29 | var rxRate: Long = 0L, 30 | 31 | // Bytes for the current session 32 | var txTotal: Long = 0L, 33 | var rxTotal: Long = 0L 34 | ) : Parcelable { 35 | operator fun plus(other: TrafficStats) = TrafficStats( 36 | txRate + other.txRate, rxRate + other.rxRate, 37 | txTotal + other.txTotal, rxTotal + other.rxTotal) 38 | 39 | constructor(parcel: Parcel) : this(parcel.readLong(), parcel.readLong(), parcel.readLong(), parcel.readLong()) 40 | override fun writeToParcel(parcel: Parcel, flags: Int) { 41 | parcel.writeLong(txRate) 42 | parcel.writeLong(rxRate) 43 | parcel.writeLong(txTotal) 44 | parcel.writeLong(rxTotal) 45 | } 46 | override fun describeContents() = 0 47 | 48 | companion object CREATOR : Parcelable.Creator { 49 | override fun createFromParcel(parcel: Parcel) = TrafficStats(parcel) 50 | override fun newArray(size: Int): Array = arrayOfNulls(size) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/bg/Executable.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2017 by Max Lv * 4 | * Copyright (C) 2017 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.cgg.clasha.bg 22 | 23 | import android.system.ErrnoException 24 | import android.system.Os 25 | import android.system.OsConstants 26 | import android.text.TextUtils 27 | import android.util.Log 28 | import com.blankj.utilcode.util.LogUtils 29 | import com.crashlytics.android.Crashlytics 30 | import com.github.cgg.clasha.App.Companion.app 31 | import com.github.cgg.clasha.JniHelper 32 | import java.io.File 33 | import java.io.FileNotFoundException 34 | 35 | object Executable { 36 | // const val REDSOCKS = "libredsocks.so" 37 | // const val SS_LOCAL = "libss-local.so" 38 | // const val SS_TUNNEL = "libss-tunnel.so" 39 | 40 | const val TUN2SOCKS = "libtun2socks.so" 41 | const val CLASH = "libclash.so" 42 | const val OVERTURE = "liboverture.so" 43 | //SS_LOCAL, SS_TUNNEL, REDSOCKS,OVERTURE 44 | private val EXECUTABLES = setOf(TUN2SOCKS, CLASH, OVERTURE) 45 | 46 | fun killAll() { 47 | for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) { 48 | val exe = File(try { 49 | File(process, "cmdline").inputStream().bufferedReader().readText() 50 | } catch (_: FileNotFoundException) { 51 | continue 52 | }.split(Character.MIN_VALUE, limit = 2).first()) 53 | if (EXECUTABLES.contains(exe.name)) try { 54 | Os.kill(process.name.toInt(), OsConstants.SIGKILL) 55 | } catch (e: ErrnoException) { 56 | if (e.errno != OsConstants.ESRCH) { 57 | e.printStackTrace() 58 | Crashlytics.log(Log.WARN, "kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed") 59 | Crashlytics.logException(e) 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/bg/LocalSocketListener.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2017 by Max Lv * 4 | * Copyright (C) 2017 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.cgg.clasha.bg 22 | 23 | import android.net.LocalServerSocket 24 | import android.net.LocalSocket 25 | import android.net.LocalSocketAddress 26 | import android.system.ErrnoException 27 | import android.system.Os 28 | import android.system.OsConstants 29 | import com.github.cgg.clasha.utils.printLog 30 | import kotlinx.coroutines.CoroutineScope 31 | import kotlinx.coroutines.channels.Channel 32 | import kotlinx.coroutines.channels.sendBlocking 33 | import kotlinx.coroutines.launch 34 | import java.io.File 35 | import java.io.IOException 36 | 37 | abstract class LocalSocketListener(name: String, socketFile: File) : Thread(name) { 38 | private val localSocket = LocalSocket().apply { 39 | socketFile.delete() // It's a must-have to close and reuse previous local socket. 40 | bind(LocalSocketAddress(socketFile.absolutePath, LocalSocketAddress.Namespace.FILESYSTEM)) 41 | } 42 | private val serverSocket = LocalServerSocket(localSocket.fileDescriptor) 43 | private val closeChannel = Channel(1) 44 | @Volatile 45 | protected var running = true 46 | 47 | /** 48 | * Inherited class do not need to close input/output streams as they will be closed automatically. 49 | */ 50 | protected open fun accept(socket: LocalSocket) = socket.use { acceptInternal(socket) } 51 | protected abstract fun acceptInternal(socket: LocalSocket) 52 | final override fun run() { 53 | localSocket.use { 54 | while (running) { 55 | try { 56 | accept(serverSocket.accept()) 57 | } catch (e: IOException) { 58 | if (running) printLog(e) 59 | continue 60 | } 61 | } 62 | } 63 | closeChannel.sendBlocking(Unit) 64 | } 65 | 66 | open fun shutdown(scope: CoroutineScope) { 67 | running = false 68 | localSocket.fileDescriptor?.apply { 69 | // see also: https://issuetracker.google.com/issues/36945762#comment15 70 | if (valid()) try { 71 | Os.shutdown(this, OsConstants.SHUT_RDWR) 72 | } catch (e: ErrnoException) { 73 | // suppress fd inactive or already closed 74 | if (e.errno != OsConstants.EBADF && e.errno != OsConstants.ENOTCONN) throw IOException(e) 75 | } 76 | } 77 | scope.launch { closeChannel.receive() } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/bg/ProxyInstance.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.bg 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.util.Log 6 | import com.blankj.utilcode.util.LogUtils 7 | import com.crashlytics.android.Crashlytics 8 | import com.github.ccg.clasha.bg.TrafficMonitor 9 | import com.github.cgg.clasha.App 10 | import com.github.cgg.clasha.data.DataStore 11 | import com.github.cgg.clasha.data.ProfileConfig 12 | import com.github.cgg.clasha.utils.getGson 13 | import kotlinx.coroutines.CoroutineScope 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.GlobalScope 16 | import kotlinx.coroutines.async 17 | import org.yaml.snakeyaml.Yaml 18 | import java.io.File 19 | import java.io.IOException 20 | import java.net.UnknownHostException 21 | 22 | /** 23 | * @Author: ccg 24 | * @Email: ccgccg2019@gmail.com 25 | * @program: ClashA 26 | * @create: 2019-03-01 27 | * @describe 28 | */ 29 | 30 | class ProxyInstance(val profile: ProfileConfig) { 31 | private var configFile: File? = null 32 | var trafficMonitor: TrafficMonitor? = null 33 | suspend fun init(service: BaseService.Interface) { 34 | 35 | 36 | //todo 这里配置DNS为空 解析系统的DNS吧 37 | // it's hard to resolve DNS on a specific interface so we'll do it here 38 | /*if (profile.host.parseNumericAddress() == null) { 39 | var retries = 0 40 | while (true) try { 41 | val io = GlobalScope.async(Dispatchers.IO) { service.resolver(profile.host) } 42 | profile.host = io.await().firstOrNull()?.hostAddress ?: throw UnknownHostException() 43 | return 44 | } catch (e: UnknownHostException) { 45 | // retries are only needed on Chrome OS where arc0 is brought up/down during VPN changes 46 | if (!DataStore.hasArc0) throw e 47 | Thread.yield() 48 | Crashlytics.log(Log.WARN, "ProxyInstance-resolver", "Retry resolving attempt #${++retries}") 49 | } 50 | }*/ 51 | 52 | 53 | } 54 | 55 | fun start(service: BaseService.Interface, stat: File, configFile: File, extraFlag: String? = null) { 56 | trafficMonitor = TrafficMonitor(stat) 57 | 58 | this.configFile = configFile 59 | val config = profile.toJson() 60 | val yaml = Yaml() 61 | configFile.writeText(yaml.dump(yaml.load(getGson().toJson(config)))) 62 | LogUtils.i(App.TAG, "Clash 配置文件准备完毕") 63 | 5450 64 | val cmd = service.buildAdditionalArguments( 65 | arrayListOf( 66 | File((service as Context).applicationInfo.nativeLibraryDir, Executable.CLASH).absolutePath, 67 | "-d", 68 | configFile?.parentFile.absolutePath 69 | ) 70 | ) 71 | 72 | service.data.processes!!.start(cmd) 73 | } 74 | 75 | fun shutdown(scope: CoroutineScope) { 76 | trafficMonitor?.apply { 77 | thread.shutdown(scope) 78 | // Make sure update total traffic when stopping the runner 79 | try { 80 | // profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition) 81 | // val profile = ProfileManager.getProfile(profile.id) ?: return 82 | // profile.tx += current.txTotal 83 | // profile.rx += current.rxTotal 84 | // ProfileManager.updateProfile(profile) 85 | } catch (e: IOException) { 86 | // if (!DataStore.directBootAware) throw e // we should only reach here because we're in direct boot 87 | // val profile = DirectBoot.getDeviceProfile()!!.toList().filterNotNull().single { it.id == profile.id } 88 | // profile.tx += current.txTotal 89 | // profile.rx += current.rxTotal 90 | // profile.dirty = true 91 | // DirectBoot.update(profile) 92 | // DirectBoot.listenForUnlock() 93 | } 94 | } 95 | trafficMonitor = null 96 | // configFile?.delete() // remove old config possibly in device storage 97 | configFile = null 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/bg/ProxyService.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.bg 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | 6 | /** 7 | * @Author: ccg 8 | * @Email: ccgccg2019@gmail.com 9 | * @program: ClashA 10 | * @create: 2019-01-06 11 | * @describe 12 | */ 13 | 14 | class ProxyService : Service(), BaseService.Interface { 15 | override val data = BaseService.Data(this) 16 | 17 | 18 | override val tag: String get() = "ClashAProxyService" 19 | override fun createNotification(profileName: String): ServiceNotification = 20 | ServiceNotification(this, "$profileName proxy mode", "service-proxy", true) 21 | 22 | override fun onBind(intent: Intent) = super.onBind(intent) 23 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = 24 | super.onStartCommand(intent, flags, startId) 25 | override fun onDestroy() { 26 | super.onDestroy() 27 | data.binder.close() 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/bg/TileService.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.bg 2 | 3 | import android.app.KeyguardManager 4 | import android.graphics.drawable.Icon 5 | import android.service.quicksettings.Tile 6 | import androidx.annotation.RequiresApi 7 | import androidx.core.content.getSystemService 8 | import com.blankj.utilcode.util.LogUtils 9 | import com.github.cgg.clasha.App.Companion.app 10 | import com.github.cgg.clasha.R 11 | import com.github.cgg.clasha.aidl.ClashAConnection 12 | import com.github.cgg.clasha.aidl.IClashAService 13 | import com.github.cgg.clasha.data.DataStore 14 | import android.service.quicksettings.TileService as BaseTileService 15 | 16 | /** 17 | * @Author: ccg 18 | * @Email: ccgccg2019@gmail.com 19 | * @program: ClashA 20 | * @create: 2019-03-11 21 | * @describe 22 | */ 23 | @RequiresApi(24) 24 | class TileService : BaseTileService(), ClashAConnection.Callback { 25 | //todo test clash logo 26 | private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_clash_logo) }//ide 27 | private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_clash_logo) }//busy 28 | private val iconConnected by lazy { Icon.createWithResource(this, R.drawable.ic_clash_logo) }//active 29 | 30 | private val keyguard by lazy { getSystemService()!! } 31 | private var tapPending = false 32 | 33 | private val connection = ClashAConnection() 34 | 35 | 36 | override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) { 37 | LogUtils.w("TileService", state, profileName, msg) 38 | updateTile(state) { profileName } 39 | } 40 | 41 | override fun onServiceConnected(service: IClashAService) { 42 | LogUtils.w("TileService", "onServiceConnected ") 43 | 44 | updateTile(BaseService.State.values()[service.state]) { service.profileName } 45 | if (tapPending) { 46 | tapPending = false 47 | onClick() 48 | } 49 | } 50 | 51 | override fun onStartListening() { 52 | super.onStartListening() 53 | connection.connect(this, this) 54 | } 55 | 56 | override fun onStopListening() { 57 | connection.disconnect(this) 58 | super.onStopListening() 59 | } 60 | 61 | override fun onClick() { 62 | if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle() 63 | } 64 | 65 | private fun updateTile(serviceState: BaseService.State, profileName: () -> String?) { 66 | qsTile?.apply { 67 | label = null 68 | when (serviceState) { 69 | BaseService.State.Idle -> throw IllegalStateException("serviceState") 70 | BaseService.State.Connecting -> { 71 | icon = iconBusy 72 | state = Tile.STATE_ACTIVE 73 | } 74 | BaseService.State.Connected -> { 75 | icon = iconConnected 76 | if (!keyguard.isDeviceLocked) label = profileName() 77 | state = Tile.STATE_ACTIVE 78 | } 79 | BaseService.State.Stopping -> { 80 | icon = iconBusy 81 | state = Tile.STATE_UNAVAILABLE 82 | } 83 | BaseService.State.Stopped -> { 84 | icon = iconIdle 85 | state = Tile.STATE_INACTIVE 86 | } 87 | } 88 | label = label ?: getString(R.string.app_name) 89 | updateTile() 90 | } 91 | } 92 | 93 | private fun toggle() { 94 | val service = connection.service 95 | if (service == null) tapPending = true else BaseService.State.values()[service.state].let { state -> 96 | when { 97 | state.canStop -> app.stopService() 98 | state == BaseService.State.Stopped -> app.startService() 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/bg/TrafficMonitor.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2017 by Max Lv * 4 | * Copyright (C) 2017 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.ccg.clasha.bg 22 | 23 | import android.net.LocalSocket 24 | import android.os.SystemClock 25 | import com.github.cgg.clasha.aidl.TrafficStats 26 | import com.github.cgg.clasha.bg.LocalSocketListener 27 | import java.io.File 28 | import java.io.IOException 29 | import java.nio.ByteBuffer 30 | import java.nio.ByteOrder 31 | 32 | class TrafficMonitor(statFile: File) { 33 | val thread = object : LocalSocketListener("TrafficMonitor-" + statFile.name, statFile) { 34 | private val buffer = ByteArray(16) 35 | private val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN) 36 | override fun acceptInternal(socket: LocalSocket) { 37 | if (socket.inputStream.read(buffer) != 16) throw IOException("Unexpected traffic stat length") 38 | val tx = stat.getLong(0) 39 | val rx = stat.getLong(8) 40 | if (current.txTotal != tx) { 41 | current.txTotal = tx 42 | dirty = true 43 | } 44 | if (current.rxTotal != rx) { 45 | current.rxTotal = rx 46 | dirty = true 47 | } 48 | } 49 | }.apply { start() } 50 | 51 | val current = TrafficStats() 52 | var out = TrafficStats() 53 | private var timestampLast = 0L 54 | private var dirty = false 55 | 56 | fun requestUpdate(): Pair { 57 | val now = SystemClock.elapsedRealtime() 58 | val delta = now - timestampLast 59 | timestampLast = now 60 | var updated = false 61 | if (delta != 0L) { 62 | if (dirty) { 63 | out = current.copy().apply { 64 | txRate = (txTotal - out.txTotal) * 1000 / delta 65 | rxRate = (rxTotal - out.rxTotal) * 1000 / delta 66 | } 67 | dirty = false 68 | updated = true 69 | } else { 70 | if (out.txRate != 0L) { 71 | out.txRate = 0 72 | updated = true 73 | } 74 | if (out.rxRate != 0L) { 75 | out.rxRate = 0 76 | updated = true 77 | } 78 | } 79 | } 80 | return Pair(out, updated) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/ConfigManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import android.database.sqlite.SQLiteCantOpenDatabaseException 4 | import com.github.cgg.clasha.utils.printLog 5 | import java.io.IOException 6 | import java.sql.SQLException 7 | 8 | /** 9 | * @Author: ccg 10 | * @Email: ccgccg2019@gmail.com 11 | * @program: ClashA 12 | * @create: 2019-03-11 13 | * @describe 14 | */ 15 | object ConfigManager { 16 | 17 | interface Listener { 18 | fun onAdd(profile: ProfileConfig) 19 | fun onRemove(profileId: Long) 20 | fun onCleared() 21 | } 22 | 23 | var listener: Listener? = null 24 | 25 | @Throws(SQLException::class) 26 | fun createProfileConfig(config: ProfileConfig = ProfileConfig()): ProfileConfig { 27 | config.id = 0 28 | config.order = PrivateDatabase.profileConfigDao.nextOrder() ?: 0 29 | config.id = PrivateDatabase.profileConfigDao.create(config) 30 | config.time = System.currentTimeMillis() 31 | listener?.onAdd(config) 32 | return config 33 | } 34 | 35 | @Throws(SQLException::class) 36 | fun getProfileConfig(id: Long): ProfileConfig? = try { 37 | PrivateDatabase.profileConfigDao[id] 38 | } catch (e: SQLiteCantOpenDatabaseException) { 39 | throw IOException(e) 40 | } catch (e: SQLException) { 41 | printLog(e) 42 | null 43 | } 44 | 45 | 46 | @Throws(IOException::class) 47 | fun getAllProfileConfigs(): List? = try { 48 | PrivateDatabase.profileConfigDao.list() 49 | } catch (ex: SQLiteCantOpenDatabaseException) { 50 | throw IOException(ex) 51 | } catch (ex: SQLException) { 52 | printLog(ex) 53 | null 54 | } 55 | 56 | @Throws(SQLException::class) 57 | fun delProfile(id: Long) { 58 | check(PrivateDatabase.profileConfigDao.delete(id) == 1) 59 | listener?.onRemove(id) 60 | //if (id in app.activeProfileIds && DataStore.directBootAware) DirectBoot.clean() 61 | } 62 | 63 | 64 | @Throws(SQLException::class) 65 | fun updateProfileConfig(profile: ProfileConfig) = check(PrivateDatabase.profileConfigDao.update(profile) == 1) 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/DateConverters.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import androidx.room.TypeConverter 4 | import java.util.* 5 | 6 | /** 7 | * @Author: ccg 8 | * @Email: ccgccg2019@gmail.com 9 | * @program: ClashA 10 | * @create: 2019-05-30 11 | * @describe 12 | */ 13 | class DateConverters { 14 | 15 | @TypeConverter 16 | fun fromTimestamp(value: Long?): Date? { 17 | return value?.let { Date(it) } 18 | } 19 | 20 | @TypeConverter 21 | fun dateToTimestamp(date: Date?): Long? { 22 | return date?.time 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/LogMessage.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import android.os.Parcelable 4 | import androidx.lifecycle.LiveData 5 | import androidx.room.* 6 | import com.github.cgg.clasha.utils.Key 7 | import kotlinx.android.parcel.Parcelize 8 | import java.io.Serializable 9 | import java.text.SimpleDateFormat 10 | import java.util.* 11 | 12 | /** 13 | * @Author: ccg 14 | * @Email: ccgccg2019@gmail.com 15 | * @program: ClashA 16 | * @create: 2019-05-28 17 | * @describe 18 | */ 19 | @Entity 20 | @Parcelize 21 | data class LogMessage constructor( 22 | @PrimaryKey(autoGenerate = true) 23 | var id: Long = 0, 24 | var content: String? = "",//rule:127 -> host,dns host -> ip 25 | var originContent: String? = "", 26 | var logType: String? = "",//DNS/rule 27 | var time: Long, 28 | var currentRunDate: Date, 29 | var profileId: Long 30 | ) : Parcelable, Serializable { 31 | 32 | @androidx.room.Dao 33 | interface Dao { 34 | @Insert 35 | fun create(value: LogMessage): Long 36 | 37 | @Query("SELECT * FROM `LogMessage` WHERE `profileId` = :id") 38 | operator fun get(id: Long): List 39 | 40 | @Query("SELECT * FROM `LogMessage` WHERE `profileId` = :id ORDER BY id DESC limit :page,${Key.LOG_PAGESIZE} ") 41 | fun getByPage(id: Long, page: Int): List 42 | 43 | @Query("DELETE FROM `LogMessage`") 44 | fun removeAll() 45 | } 46 | 47 | fun getDateFormatted(): String { 48 | return formatter.format(Date(time)).toString() 49 | } 50 | 51 | fun isRuleType(): Boolean { 52 | return Key.LOG_TYPE_RULE == logType 53 | } 54 | 55 | fun isOtherType(): Boolean { 56 | return Key.LOG_TYPE_OTHER == logType 57 | } 58 | 59 | companion object { 60 | val formatter = SimpleDateFormat("HH:mm:ss.SSS") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/LogsDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | /** 4 | * @Author: ccg 5 | * @Email: ccgccg2019@gmail.com 6 | * @program: ClashA 7 | * @create: 2019-06-15 8 | * @describe 9 | */ 10 | 11 | interface LogsDataSource { 12 | interface LoadLogsCallback { 13 | fun onLogsLoaded(logs: List) 14 | fun onDataNotAvailable() 15 | } 16 | 17 | fun getLogsByPage(profileId: Long, page: Int = 0, callback: LoadLogsCallback) 18 | 19 | fun removeAll() 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/LogsDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import androidx.room.Database 4 | import androidx.room.Room 5 | import androidx.room.RoomDatabase 6 | import androidx.room.TypeConverters 7 | import com.github.cgg.clasha.App.Companion.app 8 | import com.github.cgg.clasha.utils.Key 9 | 10 | /** 11 | * @Author: ccg 12 | * @Email: ccgccg2019@gmail.com 13 | * @program: ClashA 14 | * @create: 2019-05-30 15 | * @describe 16 | */ 17 | @Database(entities = [LogMessage::class], version = 3) 18 | @TypeConverters(DateConverters::class) 19 | abstract class LogsDatabase : RoomDatabase() { 20 | 21 | companion object { 22 | private val instance by lazy { 23 | Room.databaseBuilder(app, LogsDatabase::class.java, Key.DB_LOGS) 24 | .fallbackToDestructiveMigration() 25 | .allowMainThreadQueries() 26 | .build() 27 | } 28 | val logMessageDao get() = instance.logMessageDao() 29 | } 30 | 31 | abstract fun logMessageDao(): LogMessage.Dao 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/LogsLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import com.github.cgg.clasha.utils.AppExecutors 4 | import com.github.cgg.clasha.utils.Key.LOG_PAGESIZE 5 | 6 | /** 7 | * @Author: ccg 8 | * @Email: ccgccg2019@gmail.com 9 | * @program: ClashA 10 | * @create: 2019-06-15 11 | * @describe 12 | */ 13 | class LogsLocalDataSource 14 | private constructor( 15 | private val mAppExecutors: AppExecutors, 16 | private val logsDao: LogMessage.Dao 17 | ) : LogsDataSource { 18 | 19 | 20 | companion object { 21 | @Volatile 22 | private var instance: LogsLocalDataSource? = null 23 | 24 | fun getInstance(mAppExecutors: AppExecutors, logsDao: LogMessage.Dao) = instance ?: synchronized(this) { 25 | instance ?: LogsLocalDataSource(mAppExecutors, logsDao).also { instance = it } 26 | } 27 | } 28 | 29 | override fun getLogsByPage(profileId: Long, page: Int, callback: LogsDataSource.LoadLogsCallback) { 30 | mAppExecutors.diskIO.execute { 31 | val logs = logsDao.getByPage(profileId, page * LOG_PAGESIZE) 32 | mAppExecutors.mainThread.execute { 33 | if (logs.isEmpty()) { 34 | callback.onDataNotAvailable() 35 | } else { 36 | callback.onLogsLoaded(logs) 37 | } 38 | } 39 | } 40 | } 41 | 42 | override fun removeAll() { 43 | mAppExecutors.diskIO.execute { 44 | logsDao.removeAll() 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/LogsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import kotlin.math.log 4 | 5 | /** 6 | * @Author: ccg 7 | * @Email: ccgccg2019@gmail.com 8 | * @program: ClashA 9 | * @create: 2019-06-15 10 | * @describe 11 | */ 12 | class LogsRepository private constructor(private val logsLocalDataSource: LogsDataSource) : LogsDataSource { 13 | 14 | companion object { 15 | private var instance: LogsRepository? = null 16 | 17 | fun getInstance(logsLocalDataSource: LogsDataSource) = instance ?: synchronized(this) { 18 | instance ?: LogsRepository(logsLocalDataSource).also { instance = it } 19 | } 20 | } 21 | 22 | var cachedLogs: LinkedHashMap = LinkedHashMap() 23 | 24 | var cachedIsDirty = false 25 | 26 | override fun getLogsByPage(profileId: Long, page: Int, callback: LogsDataSource.LoadLogsCallback) { 27 | logsLocalDataSource.getLogsByPage(profileId, page, object : LogsDataSource.LoadLogsCallback { 28 | override fun onLogsLoaded(logs: List) { 29 | callback.onLogsLoaded(logs) 30 | } 31 | 32 | override fun onDataNotAvailable() { 33 | callback.onDataNotAvailable() 34 | } 35 | }) 36 | } 37 | 38 | override fun removeAll() { 39 | logsLocalDataSource.removeAll() 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/OnPreferenceDataStoreChangeListener.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2017 by Max Lv * 4 | * Copyright (C) 2017 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.cgg.clasha.data 22 | 23 | import androidx.preference.PreferenceDataStore 24 | 25 | interface OnPreferenceDataStoreChangeListener { 26 | fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/PrivateDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import androidx.room.Database 4 | import androidx.room.Room 5 | import androidx.room.RoomDatabase 6 | import androidx.room.migration.Migration 7 | import androidx.sqlite.db.SupportSQLiteDatabase 8 | import com.github.cgg.clasha.App.Companion.app 9 | import com.github.cgg.clasha.utils.Key 10 | 11 | /** 12 | * @Author: ccg 13 | * @Email: ccgccg2019@gmail.com 14 | * @program: ClashA 15 | * @create: 2019-03-11 16 | * @describe 17 | */ 18 | @Database(entities = [ProfileConfig::class], version = 4) 19 | abstract class PrivateDatabase : RoomDatabase() { 20 | companion object { 21 | private val instance by lazy { 22 | Room.databaseBuilder(app, PrivateDatabase::class.java, Key.DB_PRIVATE_CONFIG) 23 | .addMigrations(Migration3, Migration4) 24 | .fallbackToDestructiveMigration() 25 | .allowMainThreadQueries() 26 | .build() 27 | } 28 | 29 | val profileConfigDao get() = instance.profileConfigDao() 30 | } 31 | 32 | abstract fun profileConfigDao(): ProfileConfig.Dao 33 | 34 | object Migration3 : Migration(2, 3) { 35 | override fun migrate(database: SupportSQLiteDatabase) { 36 | database.execSQL("ALTER TABLE `ProfileConfig` ADD COLUMN `time` INTEGER NOT NULL DEFAULT 0") 37 | } 38 | } 39 | 40 | object Migration4 : Migration(3, 4) { 41 | override fun migrate(database: SupportSQLiteDatabase) { 42 | database.execSQL("ALTER TABLE `ProfileConfig` ADD COLUMN `selector` TEXT") 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/ProfileConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.data 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import androidx.room.* 6 | import com.blankj.utilcode.util.LogUtils 7 | import com.chad.library.adapter.base.entity.MultiItemEntity 8 | import com.github.cgg.clasha.data.LogMessage.Companion.formatter 9 | import kotlinx.android.parcel.Parcelize 10 | import org.json.JSONArray 11 | import org.json.JSONObject 12 | import org.yaml.snakeyaml.Yaml 13 | import java.io.FileInputStream 14 | import java.io.Serializable 15 | import java.text.SimpleDateFormat 16 | import java.util.* 17 | 18 | /** 19 | * @Author: ccg 20 | * @Email: ccgccg2019@gmail.com 21 | * @program: ClashA 22 | * @create: 2019-03-11 23 | * @describe 24 | */ 25 | @Entity 26 | @Parcelize 27 | data class ProfileConfig constructor( 28 | @PrimaryKey(autoGenerate = true) 29 | var id: Long = 0, 30 | var configName: String? = "", 31 | var url: String? = "", 32 | var proxyItems: String? = "", 33 | var proxyGroupItems: String? = "", 34 | var ruleItems: String? = "", 35 | var order: Long = 0, 36 | var origin: String? = "", 37 | private var itemType: Int = 0, 38 | var selector: String? = "", 39 | var time: Long = 0 40 | 41 | ) : MultiItemEntity, Parcelable, Serializable { 42 | override fun getItemType(): Int { 43 | return itemType 44 | } 45 | 46 | companion object { 47 | 48 | val DEFAULT = 0 49 | val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm") 50 | 51 | fun findProfileConfig( 52 | data: String?, 53 | profileConfig: ProfileConfig? = null, 54 | configName: String? = "Config-${Date().time}", 55 | url: String? = null 56 | ): ProfileConfig { 57 | var yaml = Yaml() 58 | var config = profileConfig ?: ProfileConfig() 59 | 60 | config.url = url 61 | config.configName = configName 62 | //remove too lagre 63 | //config.origin = data 64 | 65 | //yaml to json 66 | var jsonObject = JSONObject(yaml.load>(data)) 67 | //get proxyItems 68 | config.proxyItems = jsonObject.optJSONArray("Proxy").toString(4) 69 | //get proxyGroupItems 70 | config.proxyGroupItems = jsonObject.optJSONArray("Proxy Group").toString(4) 71 | //get ruleItems 72 | config.ruleItems = jsonObject.optJSONArray("Rule").toString(4) 73 | return config 74 | } 75 | } 76 | 77 | @androidx.room.Dao 78 | interface Dao { 79 | 80 | @Query("SELECT * FROM `ProfileConfig` WHERE `id` = :id") 81 | operator fun get(id: Long): ProfileConfig? 82 | 83 | @Insert 84 | fun create(value: ProfileConfig): Long 85 | 86 | @Update 87 | fun update(value: ProfileConfig): Int 88 | 89 | @Query("SELECT MAX(`order`) + 1 FROM `ProfileConfig`") 90 | fun nextOrder(): Long? 91 | 92 | @Query("SELECT * FROM `ProfileConfig` ORDER BY `order`") 93 | fun list(): List 94 | 95 | @Query("DELETE FROM `ProfileConfig` WHERE `id` = :id") 96 | fun delete(id: Long): Int 97 | } 98 | 99 | fun toJson(): JSONObject = JSONObject().apply { 100 | put("port", DataStore.portHttpProxy) 101 | put("socks-port", DataStore.portProxy) 102 | put("allow-lan", DataStore.allowLan) 103 | put("mode", DataStore.clashMode) 104 | put("log-level", DataStore.clashLoglevel) 105 | put( 106 | "external-controller", 107 | if (DataStore.allowLan) ("0.0.0.0:${DataStore.portApi}") else ("127.0.0.1:${DataStore.portApi}") 108 | ) 109 | 110 | put("dns", JSONObject(DataStore.dnsConfig)) 111 | LogUtils.w(this) 112 | 113 | put("Proxy", JSONArray(proxyItems)) 114 | put("Proxy Group", JSONArray(proxyGroupItems)) 115 | put("Rule", JSONArray(ruleItems)) 116 | } 117 | 118 | fun getDateFormatted(): String { 119 | return formatter.format(Date(time)).toString() 120 | } 121 | 122 | 123 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/data/db/PublicDatabase.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2017 by Max Lv * 4 | * Copyright (C) 2017 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.shadowsocks.database 22 | 23 | import androidx.room.Database 24 | import androidx.room.Room 25 | import androidx.room.RoomDatabase 26 | import com.github.cgg.clasha.App.Companion.app 27 | import com.github.cgg.clasha.data.KeyValuePair 28 | import com.github.cgg.clasha.utils.Key 29 | 30 | @Database(entities = [KeyValuePair::class], version = 1) 31 | abstract class PublicDatabase : RoomDatabase() { 32 | companion object { 33 | private val instance by lazy { 34 | Room.databaseBuilder(app.deviceStorage, PublicDatabase::class.java, Key.DB_PUBLIC) 35 | .allowMainThreadQueries() 36 | .fallbackToDestructiveMigration() 37 | .build() 38 | } 39 | 40 | val kvPairDao get() = instance.keyValuePairDao() 41 | } 42 | 43 | abstract fun keyValuePairDao(): KeyValuePair.Dao 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/net/ConcurrentLocalSocketListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.net 2 | 3 | import android.net.LocalSocket 4 | import com.github.cgg.clasha.bg.LocalSocketListener 5 | import com.github.cgg.clasha.utils.printLog 6 | import kotlinx.coroutines.* 7 | import java.io.File 8 | 9 | /** 10 | * @Author: ccg 11 | * @Email: ccgccg2019@gmail.com 12 | * @program: ClashA 13 | * @create: 2019-03-01 14 | * @describe 15 | */ 16 | 17 | abstract class ConcurrentLocalSocketListener(name: String, socketFile: File) : LocalSocketListener(name, socketFile), 18 | CoroutineScope { 19 | private val job = SupervisorJob() 20 | override val coroutineContext get() = Dispatchers.IO + job + CoroutineExceptionHandler { _, t -> printLog(t) } 21 | 22 | override fun accept(socket: LocalSocket) { 23 | launch { super.accept(socket) } 24 | } 25 | 26 | override fun shutdown(scope: CoroutineScope) { 27 | running = false 28 | job.cancel() 29 | super.shutdown(scope) 30 | scope.launch { job.join() } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/net/HTTPConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.net 2 | 3 | import android.content.Context 4 | import com.yanzhenjie.andserver.annotation.Config 5 | import com.yanzhenjie.andserver.framework.config.Multipart 6 | import com.yanzhenjie.andserver.framework.config.WebConfig 7 | import com.yanzhenjie.andserver.framework.website.AssetsWebsite 8 | import com.yanzhenjie.andserver.framework.website.Website 9 | import java.io.File 10 | 11 | /** 12 | * @Author: ccg 13 | * @Email: ccgccg2019@gmail.com 14 | * @program: ClashA 15 | * @create: 2019-09-19 16 | * @describe 17 | */ 18 | @Config 19 | class HTTPConfig : WebConfig { 20 | 21 | override fun onConfig(context: Context, delegate: WebConfig.Delegate) { 22 | delegate.addWebsite(AssetsWebsite(context, "/clash") as Website?) 23 | delegate.setMultipart( 24 | Multipart.newBuilder() 25 | .allFileMaxSize((1024 * 1024 * 20).toLong()) // 20M 26 | .fileMaxSize((1024 * 1024 * 5).toLong()) // 5M 27 | .maxInMemorySize(1024 * 10) // 1024 * 10 bytes 28 | .uploadTempDir(File(context.cacheDir, "_server_upload_cache_")) 29 | .build() 30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/net/PageController.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.net 2 | 3 | import com.yanzhenjie.andserver.annotation.Controller 4 | import com.yanzhenjie.andserver.annotation.GetMapping 5 | 6 | /** 7 | * @Author: ccg 8 | * @Email: ccgccg2019@gmail.com 9 | * @program: ClashA 10 | * @create: 2019-09-19 11 | * @describe 12 | */ 13 | @Controller 14 | class PageController { 15 | 16 | @GetMapping(path = ["/"]) 17 | fun index(): String { 18 | return "redirect:/index.html" 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/utils/AppExecutors.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.cgg.clasha.utils 18 | 19 | import android.os.Handler 20 | import android.os.Looper 21 | 22 | import java.util.concurrent.Executor 23 | import java.util.concurrent.Executors 24 | 25 | const val THREAD_COUNT = 3 26 | 27 | /** 28 | * Global executor pools for the whole application. 29 | * 30 | * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind 31 | * webservice requests). 32 | */ 33 | open class AppExecutors constructor( 34 | val diskIO: Executor = DiskIOThreadExecutor(), 35 | val networkIO: Executor = Executors.newFixedThreadPool(THREAD_COUNT), 36 | val mainThread: Executor = MainThreadExecutor(Handler(Looper.getMainLooper())) 37 | ) { 38 | 39 | open class MainThreadExecutor(handler: Handler) : Executor { 40 | private val mainThreadHandler = handler 41 | 42 | override fun execute(command: Runnable) { 43 | mainThreadHandler.post(command) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/utils/ArrayIterator.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2018 by Max Lv * 4 | * Copyright (C) 2018 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.cgg.clasha.utils 22 | 23 | import android.content.ClipData 24 | import androidx.recyclerview.widget.SortedList 25 | import org.json.JSONArray 26 | 27 | private sealed class ArrayIterator : Iterator { 28 | abstract val size: Int 29 | abstract operator fun get(index: Int): T 30 | private var count = 0 31 | override fun hasNext() = count < size 32 | override fun next(): T = if (hasNext()) this[count++] else throw NoSuchElementException() 33 | } 34 | 35 | private class ClipDataIterator(private val data: ClipData) : ArrayIterator() { 36 | override val size get() = data.itemCount 37 | override fun get(index: Int) = data.getItemAt(index) 38 | } 39 | fun ClipData.asIterable() = Iterable { ClipDataIterator(this) } 40 | 41 | private class JSONArrayIterator(private val arr: JSONArray) : ArrayIterator() { 42 | override val size get() = arr.length() 43 | override fun get(index: Int) = arr.get(index) 44 | } 45 | fun JSONArray.asIterable() = Iterable { JSONArrayIterator(this) } 46 | 47 | private class SortedListIterator(private val list: SortedList) : ArrayIterator() { 48 | override val size get() = list.size() 49 | override fun get(index: Int) = list[index] 50 | } 51 | fun SortedList.asIterable() = Iterable { SortedListIterator(this) } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/utils/DeviceStorageApp.kt: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * Copyright (C) 2017 by Max Lv * 4 | * Copyright (C) 2017 by Mygod Studio * 5 | * * 6 | * This program is free software: you can redistribute it and/or modify * 7 | * it under the terms of the GNU General Public License as published by * 8 | * the Free Software Foundation, either version 3 of the License, or * 9 | * (at your option) any later version. * 10 | * * 11 | * This program is distributed in the hope that it will be useful, * 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 14 | * GNU General Public License for more details. * 15 | * * 16 | * You should have received a copy of the GNU General Public License * 17 | * along with this program. If not, see . * 18 | * * 19 | *******************************************************************************/ 20 | 21 | package com.github.cgg.clasha.utils 22 | 23 | import android.annotation.SuppressLint 24 | import android.annotation.TargetApi 25 | import android.app.Application 26 | import android.content.Context 27 | 28 | @SuppressLint("Registered") 29 | @TargetApi(24) 30 | class DeviceStorageApp(context: Context) : Application() { 31 | init { 32 | attachBaseContext(context.createDeviceProtectedStorageContext()) 33 | } 34 | 35 | /** 36 | * Thou shalt not get the REAL underlying application context which would no longer be operating under device 37 | * protected storage. 38 | */ 39 | override fun getApplicationContext(): Context = this 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/utils/DialogHelper.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.utils 2 | 3 | import android.app.Activity 4 | import androidx.appcompat.app.AlertDialog 5 | import com.blankj.utilcode.util.PermissionUtils 6 | import com.github.cgg.clasha.R 7 | 8 | 9 | /** 10 | *
11 |  * author: Blankj
12 |  * blog  : http://blankj.com
13 |  * time  : 2018/01/10
14 |  * desc  : 对话框工具类
15 | 
* 16 | */ 17 | object DialogHelper { 18 | 19 | inline fun showRationaleDialog(shouldRequest: PermissionUtils.OnRationaleListener.ShouldRequest, activity: Activity?) { 20 | activity?.let { 21 | AlertDialog.Builder(it) 22 | .setTitle(android.R.string.dialog_alert_title) 23 | .setMessage(R.string.permission_rationale_message) 24 | .setPositiveButton(android.R.string.ok) { dialog, which -> shouldRequest.again(true) } 25 | .setNegativeButton(android.R.string.cancel) { dialog, which -> shouldRequest.again(false) } 26 | .setCancelable(false) 27 | .create() 28 | .show() 29 | } 30 | 31 | 32 | } 33 | 34 | fun showOpenAppSettingDialog(activity: Activity?) { 35 | activity?.let { 36 | AlertDialog.Builder(it) 37 | .setTitle(android.R.string.dialog_alert_title) 38 | .setMessage(R.string.permission_denied_forever_message) 39 | .setPositiveButton(android.R.string.ok) { dialog, which -> PermissionUtils.launchAppDetailsSettings() } 40 | .setNegativeButton(android.R.string.cancel) { dialog, which -> } 41 | .setCancelable(false) 42 | .create() 43 | .show() 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/utils/DiskIOThreadExecutor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.cgg.clasha.utils 17 | 18 | import java.util.concurrent.Executor 19 | import java.util.concurrent.Executors 20 | 21 | /** 22 | * Executor that runs a task on a new background thread. 23 | */ 24 | class DiskIOThreadExecutor : Executor { 25 | 26 | private val diskIO = Executors.newSingleThreadExecutor() 27 | 28 | override fun execute(command: Runnable) { diskIO.execute(command) } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/utils/YamlFileType.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.utils 2 | 3 | import com.github.cgg.clasha.R 4 | import me.rosuh.filepicker.filetype.FileType 5 | 6 | /** 7 | * @Author: ccg 8 | * @Email: ccgccg2019@gmail.com 9 | * @program: ClashA 10 | * @create: 2019-10-20 11 | * @describe 12 | */ 13 | class YamlFileType : FileType{ 14 | override val fileIconResId: Int 15 | get() = R.drawable.ic_config_yml 16 | override val fileType: String 17 | get() = "Yaml" 18 | 19 | 20 | override fun verify(fileName: String): Boolean { 21 | /** 22 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 23 | * 比如 文件名仅为:example_png 24 | */ 25 | val isHasSuffix = fileName.contains(".") 26 | if (!isHasSuffix){ 27 | // 如果没有 . 符号,即是没有文件后缀 28 | return false 29 | } 30 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 31 | return when (suffix){ 32 | "yaml"-> { 33 | true 34 | } 35 | else -> { 36 | false 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/utils/YmlFileType.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.utils 2 | 3 | import com.github.cgg.clasha.R 4 | import me.rosuh.filepicker.filetype.FileType 5 | 6 | /** 7 | * @Author: ccg 8 | * @Email: ccgccg2019@gmail.com 9 | * @program: ClashA 10 | * @create: 2019-01-03 11 | * @describe 12 | */ 13 | class YmlFileType : FileType{ 14 | override val fileIconResId: Int 15 | get() = R.drawable.ic_config_yml 16 | override val fileType: String 17 | get() = "Yml" 18 | 19 | 20 | override fun verify(fileName: String): Boolean { 21 | /** 22 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 23 | * 比如 文件名仅为:example_png 24 | */ 25 | val isHasSuffix = fileName.contains(".") 26 | if (!isHasSuffix){ 27 | // 如果没有 . 符号,即是没有文件后缀 28 | return false 29 | } 30 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 31 | return when (suffix){ 32 | "yml"-> { 33 | true 34 | } 35 | else -> { 36 | false 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/widget/ClashABottomSheetWebview.java: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.widget; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.webkit.WebView; 11 | import android.widget.FrameLayout; 12 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 13 | import com.github.cgg.clasha.R; 14 | 15 | /** 16 | * @Author: ccg 17 | * @Email: ccg 18 | * @program: BottomSheets 19 | * @create: 2018-12-19 20 | * @describe 21 | */ 22 | public class ClashABottomSheetWebview extends WebView { 23 | 24 | private CoordinatorLayout bottomCoordinator; 25 | private float downY; 26 | private float moveY; 27 | 28 | public ClashABottomSheetWebview(Context context) { 29 | super(context); 30 | } 31 | 32 | public ClashABottomSheetWebview(Context context, AttributeSet attrs) { 33 | super(context, attrs); 34 | } 35 | 36 | public ClashABottomSheetWebview(Context context, AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | } 39 | 40 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 41 | public ClashABottomSheetWebview(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 42 | super(context, attrs, defStyleAttr, defStyleRes); 43 | } 44 | 45 | public void bindBottomSheetDialog(FrameLayout container, final OnTouchListener listener) { 46 | try { 47 | bottomCoordinator = 48 | (CoordinatorLayout) container.findViewById(R.id.coordinator); 49 | setOnTouchListener(new OnTouchListener() { 50 | @Override 51 | public boolean onTouch(View v, MotionEvent event) { 52 | if (bottomCoordinator != null) { 53 | bottomCoordinator.requestDisallowInterceptTouchEvent(true); 54 | } 55 | 56 | if (listener != null) { 57 | listener.onTouch(v, event); 58 | } 59 | return false; 60 | 61 | } 62 | }); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/cgg/clasha/widget/EditTextDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.cgg.clasha.widget 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import android.text.InputType 6 | import android.view.WindowManager 7 | import android.widget.EditText 8 | import androidx.appcompat.app.AlertDialog 9 | import androidx.fragment.app.DialogFragment 10 | import com.github.cgg.clasha.R 11 | 12 | /** 13 | * @Author: ccg 14 | * @Email: ccgccg2019@gmail.com 15 | * @program: ClashA 16 | * @create: 2019-03-17 17 | * @describe 18 | */ 19 | class EditTextDialog : DialogFragment() { 20 | companion object { 21 | private const val TAG = "EditTextDialog" 22 | 23 | private const val EXTRA_TITLE = "title" 24 | private const val EXTRA_HINT = "hint" 25 | private const val EXTRA_MULTILINE = "multiline" 26 | private const val EXTRA_HAS_ONNEUTRAL = "neutral" 27 | 28 | private const val EXTRA_TEXT = "text" 29 | private const val EXTRA_TAG = "tag" 30 | 31 | 32 | fun newInstance( 33 | title: String? = null, 34 | hint: String? = null, 35 | text: String? = null, 36 | tag: String? = null, 37 | isMultiline: Boolean = false, 38 | hasOnNeutral: Boolean = false 39 | ): EditTextDialog { 40 | val dialog = EditTextDialog() 41 | val args = Bundle().apply { 42 | putString(EXTRA_TITLE, title) 43 | putString(EXTRA_HINT, hint) 44 | putString(EXTRA_TEXT, text) 45 | putString(EXTRA_TAG, tag) 46 | putBoolean(EXTRA_MULTILINE, isMultiline) 47 | putBoolean(EXTRA_HAS_ONNEUTRAL, hasOnNeutral) 48 | } 49 | dialog.arguments = args 50 | return dialog 51 | } 52 | } 53 | 54 | lateinit var editText: EditText 55 | var onOk: ((editText: EditText, tag: String) -> Unit)? = null 56 | var onCancel: (() -> Unit)? = null 57 | var onNeutral: ((editText: EditText, tag: String) -> Unit)? = null 58 | 59 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 60 | val title = arguments?.getString(EXTRA_TITLE) 61 | val hint = arguments?.getString(EXTRA_HINT) 62 | val text: String? = arguments?.getString(EXTRA_TEXT) 63 | val tag: String = arguments?.getString(EXTRA_TAG) ?: "" 64 | val hasOnNeutral = arguments?.getBoolean(EXTRA_HAS_ONNEUTRAL) ?: false 65 | val isMiltiline = arguments?.getBoolean(EXTRA_MULTILINE) ?: false 66 | 67 | // StackOverflowError 68 | // val view = layoutInflater.inflate(R.layout.dialog_edit_text, null) 69 | val view = activity!!.layoutInflater.inflate(R.layout.dialog_edit_text, null) 70 | 71 | editText = view.findViewById(R.id.editText) 72 | editText.hint = hint 73 | 74 | if (isMiltiline) { 75 | editText.minLines = 3 76 | editText.inputType = 77 | InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS 78 | } 79 | if (text != null) { 80 | // editText.setText(text) 81 | // editText.setSelection(text.length) 82 | editText.append(text) 83 | } 84 | 85 | val builder = AlertDialog.Builder(context!!) 86 | .setTitle(title) 87 | .setView(view) 88 | 89 | .setPositiveButton(android.R.string.ok) { _, _ -> 90 | onOk?.invoke(editText, tag) 91 | } 92 | .setNegativeButton(android.R.string.cancel) { _, _ -> 93 | onCancel?.invoke() 94 | } 95 | if (hasOnNeutral) { 96 | builder.setNeutralButton(R.string.dialog_delete) { _, _ -> 97 | onNeutral?.invoke(editText, tag) 98 | } 99 | } 100 | val dialog = builder.create() 101 | 102 | dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) 103 | 104 | return dialog 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /app/src/main/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_ABI := armeabi-v7a arm64-v8a x86 2 | APP_PLATFORM := android-21 3 | APP_STL := c++_static 4 | NDK_TOOLCHAIN_VERSION := clang 5 | -------------------------------------------------------------------------------- /app/src/main/jni/build-shared-executable.mk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # this file is included from Android.mk files to build a target-specific 16 | # executable program 17 | # 18 | # Modified by @Mygod, based on: 19 | # https://android.googlesource.com/platform/ndk/+/a355a4e/build/core/build-shared-library.mk 20 | # https://android.googlesource.com/platform/ndk/+/a355a4e/build/core/build-executable.mk 21 | LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE 22 | LOCAL_MAKEFILE := $(local-makefile) 23 | $(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT)) 24 | $(call check-LOCAL_MODULE,$(LOCAL_MAKEFILE)) 25 | $(call check-LOCAL_MODULE_FILENAME) 26 | # we are building target objects 27 | my := TARGET_ 28 | $(call handle-module-filename,lib,$(TARGET_SONAME_EXTENSION)) 29 | $(call handle-module-built) 30 | LOCAL_MODULE_CLASS := EXECUTABLE 31 | include $(BUILD_SYSTEM)/build-module.mk 32 | -------------------------------------------------------------------------------- /app/src/main/res/color/background_service.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/color/nav_item_tint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_config_yml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccg2018/ClashA/3207923a14dd15982dc5c1f97e819a11d573b972/app/src/main/res/drawable-hdpi/ic_config_yml.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_config_yml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccg2018/ClashA/3207923a14dd15982dc5c1f97e819a11d573b972/app/src/main/res/drawable-mdpi/ic_config_yml.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_config_yml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccg2018/ClashA/3207923a14dd15982dc5c1f97e819a11d573b972/app/src/main/res/drawable-xhdpi/ic_config_yml.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_config_yml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccg2018/ClashA/3207923a14dd15982dc5c1f97e819a11d573b972/app/src/main/res/drawable-xxhdpi/ic_config_yml.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_config_yml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccg2018/ClashA/3207923a14dd15982dc5c1f97e819a11d573b972/app/src/main/res/drawable-xxxhdpi/ic_config_yml.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_log_num.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_toolbar_radius.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_description.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_dns.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_logs.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clash_logo.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_device_data_usage.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_send.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navigation_close.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navigation_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh_update.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_active.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_busy.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_connected.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_connecting.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_idle.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_stopped.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_stopping.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress_horizontal_wv.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 |