Jelajahi Sumber

feat: add intent filters for tasker automation (#119)

Steve Johnson 2 tahun lalu
induk
melakukan
021939264f

+ 13 - 0
README.md

@@ -47,6 +47,19 @@ Feature of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
    ./gradlew app:assembleMeta-AlphaRelease
    ```
 
+### Automation
+
+APP package name is `com.github.metacubex.clash.meta`
+
+- Toggle Clash.Meta service status
+  - Send intent to `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.TOGGLE_CLASH`
+- Start Clash.Meta service
+  - Send intent to `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.START_CLASH`
+- Stop Clash.Meta service
+  - Send intent to `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.STOP_CLASH`
+- Import a profile
+  - URL Scheme `clash://install-config?url=<encoded URI>` or `clashmeta://install-config?url=<encoded URI>`
+
 ### Kernel Contribution
 
 - CMFA uses the kernel from `android-real` branch under `MetaCubeX/Clash.Meta`, which is a merge of the main `Alpha` branch and `android-open`.

+ 14 - 2
app/src/main/AndroidManifest.xml

@@ -55,9 +55,9 @@
             </intent-filter>
         </activity>
         <activity
-            android:name=".ExternalImportActivity"
+            android:name=".ExternalControlActivity"
             android:exported="true"
-            android:label="@string/import_from_file"
+            android:label="@string/external_control_activity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
@@ -69,6 +69,18 @@
                 <data android:scheme="clashmeta"/>
                 <data android:host="install-config"/>
             </intent-filter>
+            <intent-filter>
+                <action android:name="com.github.metacubex.clash.meta.action.START_CLASH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.github.metacubex.clash.meta.action.STOP_CLASH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.github.metacubex.clash.meta.action.TOGGLE_CLASH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
         <activity
             android:name=".ApkBrokenActivity"

+ 92 - 0
app/src/main/java/com/github/kr328/clash/ExternalControlActivity.kt

@@ -0,0 +1,92 @@
+package com.github.kr328.clash
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import com.github.kr328.clash.common.constants.Intents
+import com.github.kr328.clash.common.util.intent
+import com.github.kr328.clash.common.util.setUUID
+import com.github.kr328.clash.design.MainDesign
+import com.github.kr328.clash.design.ui.ToastDuration
+import com.github.kr328.clash.remote.Remote
+import com.github.kr328.clash.remote.StatusClient
+import com.github.kr328.clash.service.model.Profile
+import com.github.kr328.clash.util.startClashService
+import com.github.kr328.clash.util.stopClashService
+import com.github.kr328.clash.util.withProfile
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.launch
+import java.util.*
+
+class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        when(intent.action) {
+            Intent.ACTION_VIEW -> {
+                val uri = intent.data ?: return finish()
+                val url = uri.getQueryParameter("url") ?: return finish()
+
+                launch {
+                    val uuid = withProfile {
+                        val type = when (uri.getQueryParameter("type")?.lowercase(Locale.getDefault())) {
+                            "url" -> Profile.Type.Url
+                            "file" -> Profile.Type.File
+                            else -> Profile.Type.Url
+                        }
+                        val name = uri.getQueryParameter("name") ?: getString(R.string.new_profile)
+
+                        create(type, name).also {
+                            patch(it, name, url, 0)
+                        }
+                    }
+                    startActivity(PropertiesActivity::class.intent.setUUID(uuid))
+                    finish()
+                }
+            }
+
+            Intents.ACTION_TOGGLE_CLASH -> if(Remote.broadcasts.clashRunning) {
+                stopClash()
+            }
+            else {
+                startClash()
+            }
+
+            Intents.ACTION_START_CLASH -> if(!Remote.broadcasts.clashRunning) {
+                startClash()
+            }
+            else {
+                Toast.makeText(this, R.string.external_control_started, Toast.LENGTH_LONG).show()
+            }
+
+            Intents.ACTION_STOP_CLASH -> if(Remote.broadcasts.clashRunning) {
+                stopClash()
+            }
+            else {
+                Toast.makeText(this, R.string.external_control_stopped, Toast.LENGTH_LONG).show()
+            }
+        }
+        return finish()
+    }
+
+    private fun startClash() {
+//        if (currentProfile == null) {
+//            Toast.makeText(this, R.string.no_profile_selected, Toast.LENGTH_LONG).show()
+//            return
+//        }
+        val vpnRequest = startClashService()
+        if (vpnRequest != null) {
+            Toast.makeText(this, R.string.unable_to_start_vpn, Toast.LENGTH_LONG).show()
+            return
+        }
+        Toast.makeText(this, R.string.external_control_started, Toast.LENGTH_LONG).show()
+    }
+
+    private fun stopClash() {
+        stopClashService()
+        Toast.makeText(this, R.string.external_control_stopped, Toast.LENGTH_LONG).show()
+    }
+}

+ 0 - 44
app/src/main/java/com/github/kr328/clash/ExternalImportActivity.kt

@@ -1,44 +0,0 @@
-package com.github.kr328.clash
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import com.github.kr328.clash.common.util.intent
-import com.github.kr328.clash.common.util.setUUID
-import com.github.kr328.clash.service.model.Profile
-import com.github.kr328.clash.util.withProfile
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.launch
-import java.util.*
-
-class ExternalImportActivity : Activity(), CoroutineScope by MainScope() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        if (intent.action != Intent.ACTION_VIEW)
-            return finish()
-
-        val uri = intent.data ?: return finish()
-        val url = uri.getQueryParameter("url") ?: return finish()
-
-        launch {
-            val uuid = withProfile {
-                val type = when (uri.getQueryParameter("type")?.lowercase(Locale.getDefault())) {
-                    "url" -> Profile.Type.Url
-                    "file" -> Profile.Type.File
-                    else -> Profile.Type.Url
-                }
-                val name = uri.getQueryParameter("name") ?: getString(R.string.new_profile)
-
-                create(type, name).also {
-                    patch(it, name, url, 0)
-                }
-            }
-
-            startActivity(PropertiesActivity::class.intent.setUUID(uuid))
-
-            finish()
-        }
-    }
-}

+ 3 - 0
common/src/main/java/com/github/kr328/clash/common/constants/Intents.kt

@@ -5,6 +5,9 @@ import com.github.kr328.clash.common.util.packageName
 object Intents {
     // Public
     val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL"
+    val ACTION_START_CLASH = "$packageName.action.START_CLASH"
+    val ACTION_STOP_CLASH = "$packageName.action.STOP_CLASH"
+    val ACTION_TOGGLE_CLASH = "$packageName.action.TOGGLE_CLASH"
 
     const val EXTRA_NAME = "name"
 

+ 3 - 0
design/src/main/res/values-ja-rJP/strings.xml

@@ -245,4 +245,7 @@
     <string name="geofile_imported">%1$s imported</string>
     <string name="toast_profile_updated_complete">Update profile %s completed</string>
     <string name="toast_profile_updated_failed">Update profile %1$s failed: %2$s</string>
+    <string name="external_control_activity">External Control</string>
+    <string name="external_control_started">Clash.Meta service started</string>
+    <string name="external_control_stopped">Clash.Meta service stopped</string>
 </resources>

+ 3 - 0
design/src/main/res/values-ko-rKR/strings.xml

@@ -245,4 +245,7 @@
     <string name="geofile_imported">%1$s imported</string>
     <string name="toast_profile_updated_complete">Update profile %s completed</string>
     <string name="toast_profile_updated_failed">Update profile %1$s failed: %2$s</string>
+    <string name="external_control_activity">External Control</string>
+    <string name="external_control_started">Clash.Meta service started</string>
+    <string name="external_control_stopped">Clash.Meta service stopped</string>
 </resources>

+ 3 - 0
design/src/main/res/values-ru/strings.xml

@@ -310,4 +310,7 @@
     <string name="geofile_imported">%1$s imported</string>
     <string name="toast_profile_updated_complete">Update profile %s completed</string>
     <string name="toast_profile_updated_failed">Update profile %1$s failed: %2$s</string>
+    <string name="external_control_activity">External Control</string>
+    <string name="external_control_started">Clash.Meta service started</string>
+    <string name="external_control_stopped">Clash.Meta service stopped</string>
 </resources>

+ 3 - 0
design/src/main/res/values-zh-rHK/strings.xml

@@ -242,4 +242,7 @@
     <string name="geofile_imported">%1$s imported</string>
     <string name="toast_profile_updated_complete">Update profile %s completed</string>
     <string name="toast_profile_updated_failed">Update profile %1$s failed: %2$s</string>
+    <string name="external_control_activity">External Control</string>
+    <string name="external_control_started">Clash.Meta service started</string>
+    <string name="external_control_stopped">Clash.Meta service stopped</string>
 </resources>

+ 3 - 0
design/src/main/res/values-zh-rTW/strings.xml

@@ -242,4 +242,7 @@
     <string name="geofile_imported">%1$s imported</string>
     <string name="toast_profile_updated_complete">Update profile %s completed</string>
     <string name="toast_profile_updated_failed">Update profile %1$s failed: %2$s</string>
+    <string name="external_control_activity">External Control</string>
+    <string name="external_control_started">Clash.Meta service started</string>
+    <string name="external_control_stopped">Clash.Meta service stopped</string>
 </resources>

+ 3 - 0
design/src/main/res/values-zh/strings.xml

@@ -245,4 +245,7 @@
     <string name="geofile_imported">%1$s 已导入</string>
     <string name="toast_profile_updated_complete">更新配置 %s 成功</string>
     <string name="toast_profile_updated_failed">更新配置 %1$s 失败: %2$s</string>
+    <string name="external_control_activity">External Control</string>
+    <string name="external_control_started">Clash.Meta 服务已启动</string>
+    <string name="external_control_stopped">Clash.Meta 服务已停止</string>
 </resources>

+ 3 - 0
design/src/main/res/values/strings.xml

@@ -310,4 +310,7 @@
     <string name="geofile_imported">%1$s imported</string>
     <string name="toast_profile_updated_complete">Update profile %s completed</string>
     <string name="toast_profile_updated_failed">Update profile %1$s failed: %2$s</string>
+    <string name="external_control_activity">External Control</string>
+    <string name="external_control_started">Clash.Meta service started</string>
+    <string name="external_control_stopped">Clash.Meta service stopped</string>
 </resources>