Skip to content

Commit 83261bb

Browse files
authored
Add dynamic App Shortcuts for external automation (Tasker/Samsung Routines) (#676)
Register toggle/start/stop shortcuts via ShortcutManagerCompat on app launch, enabling Samsung Routines and other launchers to control the Clash service through long-press app icon shortcuts. Use launcher icon for shortcut display; suppress activity transitions and exclude ExternalControlActivity from recents for seamless background control.
1 parent 406e400 commit 83261bb

5 files changed

Lines changed: 76 additions & 0 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@
6565

6666
<activity
6767
android:name=".ExternalControlActivity"
68+
android:excludeFromRecents="true"
6869
android:exported="true"
6970
android:label="@string/external_control_activity"
71+
android:noHistory="true"
7072
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
7173
<intent-filter>
7274
<action android:name="android.intent.action.VIEW" />

app/src/main/java/com/github/kr328/clash/ExternalControlActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import com.github.kr328.clash.design.R
2525
class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
2626
override fun onCreate(savedInstanceState: Bundle?) {
2727
super.onCreate(savedInstanceState)
28+
@Suppress("DEPRECATION")
29+
overridePendingTransition(0, 0)
2830

2931
when(intent.action) {
3032
Intent.ACTION_VIEW -> {
@@ -90,4 +92,10 @@ class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
9092
stopClashService()
9193
Toast.makeText(this, R.string.external_control_stopped, Toast.LENGTH_LONG).show()
9294
}
95+
96+
override fun finish() {
97+
super.finish()
98+
@Suppress("DEPRECATION")
99+
overridePendingTransition(0, 0)
100+
}
93101
}

app/src/main/java/com/github/kr328/clash/MainApplication.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@ package com.github.kr328.clash
22

33
import android.app.Application
44
import android.content.Context
5+
import android.content.Intent
6+
import androidx.core.content.pm.ShortcutInfoCompat
7+
import androidx.core.content.pm.ShortcutManagerCompat
8+
import androidx.core.graphics.drawable.IconCompat
59
import com.github.kr328.clash.common.Global
610
import com.github.kr328.clash.common.compat.currentProcessName
11+
import com.github.kr328.clash.common.constants.Intents
712
import com.github.kr328.clash.common.log.Log
813
import com.github.kr328.clash.remote.Remote
914
import com.github.kr328.clash.service.util.sendServiceRecreated
1015
import com.github.kr328.clash.util.clashDir
1116
import java.io.File
1217
import java.io.FileOutputStream
18+
import com.github.kr328.clash.design.R as DesignR
1319

1420

1521
@Suppress("unused")
@@ -30,11 +36,57 @@ class MainApplication : Application() {
3036

3137
if (processName == packageName) {
3238
Remote.launch()
39+
setupShortcuts()
3340
} else {
3441
sendServiceRecreated()
3542
}
3643
}
3744

45+
private fun setupShortcuts() {
46+
val icon = IconCompat.createWithResource(this, R.mipmap.ic_launcher)
47+
val flags = Intent.FLAG_ACTIVITY_NEW_TASK or
48+
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or
49+
Intent.FLAG_ACTIVITY_NO_ANIMATION
50+
51+
val toggle = ShortcutInfoCompat.Builder(this, "toggle_clash")
52+
.setShortLabel(getString(DesignR.string.shortcut_toggle_short))
53+
.setLongLabel(getString(DesignR.string.shortcut_toggle_long))
54+
.setIcon(icon)
55+
.setIntent(
56+
Intent(Intents.ACTION_TOGGLE_CLASH)
57+
.setClassName(this, ExternalControlActivity::class.java.name)
58+
.addFlags(flags)
59+
)
60+
.setRank(0)
61+
.build()
62+
63+
val start = ShortcutInfoCompat.Builder(this, "start_clash")
64+
.setShortLabel(getString(DesignR.string.shortcut_start_short))
65+
.setLongLabel(getString(DesignR.string.shortcut_start_long))
66+
.setIcon(icon)
67+
.setIntent(
68+
Intent(Intents.ACTION_START_CLASH)
69+
.setClassName(this, ExternalControlActivity::class.java.name)
70+
.addFlags(flags)
71+
)
72+
.setRank(1)
73+
.build()
74+
75+
val stop = ShortcutInfoCompat.Builder(this, "stop_clash")
76+
.setShortLabel(getString(DesignR.string.shortcut_stop_short))
77+
.setLongLabel(getString(DesignR.string.shortcut_stop_long))
78+
.setIcon(icon)
79+
.setIntent(
80+
Intent(Intents.ACTION_STOP_CLASH)
81+
.setClassName(this, ExternalControlActivity::class.java.name)
82+
.addFlags(flags)
83+
)
84+
.setRank(2)
85+
.build()
86+
87+
ShortcutManagerCompat.setDynamicShortcuts(this, listOf(toggle, start, stop))
88+
}
89+
3890
private fun extractGeoFiles() {
3991
clashDir.mkdirs()
4092

design/src/main/res/values-zh/strings.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,11 @@
262262
<string name="external_control_stopped">Clash.Meta 服务已停止</string>
263263
<string name="import_from_qr_no_permission">相机权限受限,请前往设置开启。</string>
264264
<string name="import_from_qr_exception">发生系统未知异常,操作失败。</string>
265+
266+
<string name="shortcut_toggle_short">切换 Clash</string>
267+
<string name="shortcut_toggle_long">切换 Clash 服务启停</string>
268+
<string name="shortcut_start_short">启动 Clash</string>
269+
<string name="shortcut_start_long">启动 Clash 服务</string>
270+
<string name="shortcut_stop_short">停止 Clash</string>
271+
<string name="shortcut_stop_long">停止 Clash 服务</string>
265272
</resources>

design/src/main/res/values/strings.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,4 +350,11 @@
350350
<string name="hide_from_recents_desc">Hide app from the Recent apps screen</string>
351351
<string name="import_from_qr_no_permission">Camera access is restricted. Please enable it in Settings.</string>
352352
<string name="import_from_qr_exception">An unhandled system exception occurred.</string>
353+
354+
<string name="shortcut_toggle_short">Toggle Clash</string>
355+
<string name="shortcut_toggle_long">Toggle Clash service on/off</string>
356+
<string name="shortcut_start_short">Start Clash</string>
357+
<string name="shortcut_start_long">Start Clash service</string>
358+
<string name="shortcut_stop_short">Stop Clash</string>
359+
<string name="shortcut_stop_long">Stop Clash service</string>
353360
</resources>

0 commit comments

Comments
 (0)