├── .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 |
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