Skip to content

Commit af367db

Browse files
committed
hide preview
aigc
1 parent a277921 commit af367db

7 files changed

Lines changed: 361 additions & 92 deletions

File tree

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Hide App from Recent Task List
22

3-
Simple module to hide any app from recent task list.
3+
Simple module to hide apps from the recent task list, or keep them visible while hiding their preview snapshots.
44

55
Designed in pure Kotlin & Jetpack Compose & Material Design 3. Can be a template for any Xposed module with an application selection list.
66

@@ -12,8 +12,8 @@ Designed in pure Kotlin & Jetpack Compose & Material Design 3. Can be a template
1212
1313
1. Select `System framework` (package name may be `android` or `system` or empty, [see this](https://github.com/LSPosed/LSPosed/releases/tag/v1.9.1)) in module scope and activate the module
1414
2. Force stop module
15-
3. Select the apps you want to hide from recent app list in module settings (if package list not shown, you can manually import / export settings to edit config)
16-
4. Reboot (you MUST reboot when you modify the list, or changes will not be applied until next reboot)
15+
3. Set a mode for each app in module settings: `No action`, `Hide task`, or `Hide preview` (if package list not shown, you can manually import / export settings to edit config)
16+
4. Reboot (you MUST reboot when you modify the list or the hide mode, or changes will not be applied until next reboot)
1717
5. If you need multi-user support, install this module only in main user, and use [Shizuku](https://shizuku.rikka.app/download/) to get app info from other users.
1818

1919
## Module Scope
@@ -30,7 +30,9 @@ Xposed Modules Repo URL: <https://github.com/Xposed-Modules-Repo/moe.lyniko.hide
3030

3131
UI: Material Design 3 + Jetpack Compose + Kotlin.
3232

33-
Hook: Hook `com.android.server.wm.RecentTasks.isVisibleRecentTask(com.android.server.wm.Task)`, `(callMethod(param.args[0], "getBaseIntent") as Intent).component?.packageName` is package name.
33+
Hook: Hook `com.android.server.wm.RecentTasks.isVisibleRecentTask(com.android.server.wm.Task)` for apps set to `Hide task`.
34+
35+
For apps set to `Hide preview`, hook `com.android.server.wm.TaskSnapshotController.getSnapshotMode(com.android.server.wm.Task)` and `com.android.server.wm.ActivityRecord.shouldUseAppThemeSnapshot()` so selected apps use an app-theme task snapshot instead of a real screenshot. `com.android.server.wm.Task.getSnapshot(boolean, boolean)` is also hooked to avoid returning previously cached real snapshots.
3436

3537
## HELP ME IT DOESNT WORK!!!
3638

Lines changed: 164 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package moe.lyniko.hiderecent
22

3+
import android.content.ComponentName
34
import android.content.Intent
45
import de.robv.android.xposed.IXposedHookLoadPackage
56
import de.robv.android.xposed.XC_MethodHook
7+
import de.robv.android.xposed.XposedBridge
68
import de.robv.android.xposed.XSharedPreferences
79
import de.robv.android.xposed.XposedHelpers.callMethod
810
import de.robv.android.xposed.XposedHelpers.findAndHookMethod
11+
import de.robv.android.xposed.XposedHelpers.getObjectField
912
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam
10-
import de.robv.android.xposed.XposedBridge
1113
import moe.lyniko.hiderecent.utils.PreferenceUtils
1214

1315
class MainHook : IXposedHookLoadPackage {
@@ -17,85 +19,191 @@ class MainHook : IXposedHookLoadPackage {
1719
}
1820

1921
private fun onAppHooked(lpparam: LoadPackageParam) {
20-
val visibleFilterHook: XC_MethodHook = object : XC_MethodHook() {
21-
override fun beforeHookedMethod(param: MethodHookParam) {
22-
if (!loaded) tryLoadPackages()
23-
24-
val taskObject = param.args[0]
25-
26-
// 1. Safely call getBaseIntent
27-
val intent = try {
28-
callMethod(taskObject, "getBaseIntent") as? Intent
29-
} catch (t: Throwable) {
30-
XposedBridge.log("HideRecent: Failed to call getBaseIntent(). Error: ${t.message}")
31-
null
32-
}
33-
34-
if (intent == null) {
35-
XposedBridge.log("HideRecent: getBaseIntent() returned null for task: $taskObject")
36-
return
37-
}
38-
39-
// 2. Try to get package name from component or package attribute
40-
var packageName = intent.component?.packageName ?: intent.`package`
41-
42-
// 3. Fallback: Try to reflect the internal intent field directly if packageName is still null
43-
if (packageName == null) {
44-
try {
45-
val realIntent = callMethod(taskObject, "intent") as? Intent
46-
if (realIntent != null) {
47-
packageName = realIntent.component?.packageName ?: realIntent.`package`
48-
} else {
49-
XposedBridge.log("HideRecent: Internal intent field is also null")
22+
installHook("RecentTasks.isVisibleRecentTask") {
23+
findAndHookMethod(
24+
"com.android.server.wm.RecentTasks",
25+
lpparam.classLoader,
26+
"isVisibleRecentTask",
27+
"com.android.server.wm.Task",
28+
object : XC_MethodHook() {
29+
override fun beforeHookedMethod(param: MethodHookParam) {
30+
if (shouldRemoveTask(param.args[0])) {
31+
param.result = false
5032
}
51-
} catch (t: Throwable) {
52-
XposedBridge.log("HideRecent: Failed to reflect internal intent field. Error: ${t.message}")
5333
}
5434
}
35+
)
36+
}
5537

56-
// 4. Final check for package name
57-
if (packageName == null) {
58-
XposedBridge.log("HideRecent: Cannot resolve packageName for intent: $intent")
59-
return
38+
installHook("TaskSnapshotController.getSnapshotMode") {
39+
findAndHookMethod(
40+
"com.android.server.wm.TaskSnapshotController",
41+
lpparam.classLoader,
42+
"getSnapshotMode",
43+
"com.android.server.wm.Task",
44+
object : XC_MethodHook() {
45+
override fun beforeHookedMethod(param: MethodHookParam) {
46+
if (shouldHideTaskContent(param.args[0])) {
47+
param.result = SNAPSHOT_MODE_APP_THEME
48+
}
49+
}
6050
}
51+
)
52+
}
6153

62-
// 5. Match the blocklist and intercept
63-
if (packages.contains(packageName)) {
64-
param.result = false
54+
installHook("ActivityRecord.shouldUseAppThemeSnapshot") {
55+
findAndHookMethod(
56+
"com.android.server.wm.ActivityRecord",
57+
lpparam.classLoader,
58+
"shouldUseAppThemeSnapshot",
59+
object : XC_MethodHook() {
60+
override fun beforeHookedMethod(param: MethodHookParam) {
61+
if (shouldHideActivityContent(param.thisObject)) {
62+
param.result = true
63+
}
64+
}
6565
}
66-
}
66+
)
6767
}
68-
try {
68+
69+
installHook("Task.getSnapshot") {
6970
findAndHookMethod(
70-
"com.android.server.wm.RecentTasks",
71-
lpparam.classLoader,
72-
"isVisibleRecentTask",
7371
"com.android.server.wm.Task",
74-
visibleFilterHook
72+
lpparam.classLoader,
73+
"getSnapshot",
74+
Boolean::class.javaPrimitiveType!!,
75+
Boolean::class.javaPrimitiveType!!,
76+
object : XC_MethodHook() {
77+
override fun afterHookedMethod(param: MethodHookParam) {
78+
val snapshot = param.result ?: return
79+
if (shouldHideTaskContent(param.thisObject) && isRealSnapshot(snapshot)) {
80+
param.result = null
81+
}
82+
}
83+
}
7584
)
76-
XposedBridge.log("HideRecent: hook installed, packages=$packages")
85+
}
86+
}
87+
88+
private fun installHook(name: String, hookInstaller: () -> Unit) {
89+
try {
90+
hookInstaller()
91+
XposedBridge.log("HideRecent: hook installed: $name")
7792
} catch (t: Throwable) {
78-
XposedBridge.log("HideRecent: hook FAILED")
93+
XposedBridge.log("HideRecent: hook FAILED: $name")
7994
XposedBridge.log(t)
8095
}
8196
}
8297

98+
private fun shouldRemoveTask(taskObject: Any?): Boolean {
99+
if (!loaded) tryLoadConfig()
100+
return isTaskHiddenPackage(packageNameFromTask(taskObject))
101+
}
102+
103+
private fun shouldHideTaskContent(taskObject: Any?): Boolean {
104+
if (!loaded) tryLoadConfig()
105+
return isContentHiddenPackage(packageNameFromTask(taskObject))
106+
}
107+
108+
private fun shouldHideActivityContent(activityObject: Any?): Boolean {
109+
if (!loaded) tryLoadConfig()
110+
return isContentHiddenPackage(packageNameFromActivityRecord(activityObject))
111+
}
112+
113+
private fun isTaskHiddenPackage(packageName: String?): Boolean {
114+
return packageName != null && taskHiddenPackages.contains(packageName)
115+
}
116+
117+
private fun isContentHiddenPackage(packageName: String?): Boolean {
118+
return packageName != null && contentHiddenPackages.contains(packageName)
119+
}
120+
121+
private fun packageNameFromTask(taskObject: Any?): String? {
122+
if (taskObject == null) return null
123+
124+
val baseIntent = tryOrNull {
125+
callMethod(taskObject, "getBaseIntent") as? Intent
126+
}
127+
packageNameFromIntent(baseIntent)?.let { return it }
128+
129+
val taskIntent = tryOrNull {
130+
getObjectField(taskObject, "intent") as? Intent
131+
}
132+
packageNameFromIntent(taskIntent)?.let { return it }
133+
134+
val realActivity = tryOrNull {
135+
getObjectField(taskObject, "realActivity") as? ComponentName
136+
}
137+
realActivity?.packageName?.let { return it }
138+
139+
return tryOrNull {
140+
getObjectField(taskObject, "mCallingPackage") as? String
141+
}
142+
}
143+
144+
private fun packageNameFromActivityRecord(activityObject: Any?): String? {
145+
if (activityObject == null) return null
146+
147+
val packageName = tryOrNull {
148+
getObjectField(activityObject, "packageName") as? String
149+
}
150+
packageName?.let { return it }
151+
152+
val activityComponent = tryOrNull {
153+
getObjectField(activityObject, "mActivityComponent") as? ComponentName
154+
}
155+
activityComponent?.packageName?.let { return it }
156+
157+
val intent = tryOrNull {
158+
getObjectField(activityObject, "intent") as? Intent
159+
}
160+
return packageNameFromIntent(intent)
161+
}
162+
163+
private fun packageNameFromIntent(intent: Intent?): String? {
164+
return intent?.component?.packageName ?: intent?.`package`
165+
}
166+
167+
private fun isRealSnapshot(snapshot: Any): Boolean {
168+
return tryOrNull {
169+
callMethod(snapshot, "isRealSnapshot") as? Boolean
170+
} ?: true
171+
}
172+
173+
private inline fun <T> tryOrNull(block: () -> T): T? {
174+
return try {
175+
block()
176+
} catch (_: Throwable) {
177+
null
178+
}
179+
}
180+
83181
@Volatile
84-
private var packages: Set<String> = emptySet()
182+
private var taskHiddenPackages: Set<String> = emptySet()
183+
@Volatile
184+
private var contentHiddenPackages: Set<String> = emptySet()
85185
@Volatile
86186
private var loaded = false
87187

88-
private fun tryLoadPackages() {
89-
val xsp = XSharedPreferences(BuildConfig.APPLICATION_ID, PreferenceUtils.functionalConfigName)
90-
xsp.makeWorldReadable()
91-
val pkgs = PreferenceUtils.getPackageListFromPref(xsp)
92-
if (pkgs.isNotEmpty()) {
93-
packages = pkgs
188+
private fun tryLoadConfig() {
189+
val packagePref = XSharedPreferences(BuildConfig.APPLICATION_ID, PreferenceUtils.functionalConfigName)
190+
packagePref.makeWorldReadable()
191+
val hiddenTasks = PreferenceUtils.getPackageListFromPref(packagePref)
192+
val hiddenContents = PreferenceUtils.getContentHiddenPackageListFromPref(packagePref)
193+
hiddenContents.removeAll(hiddenTasks)
194+
taskHiddenPackages = hiddenTasks
195+
contentHiddenPackages = hiddenContents
196+
197+
if (hiddenTasks.isNotEmpty() || hiddenContents.isNotEmpty()) {
94198
loaded = true
95199
}
96200
}
97201

202+
companion object {
203+
private const val SNAPSHOT_MODE_APP_THEME = 1
204+
}
205+
98206
init {
99-
tryLoadPackages()
207+
tryLoadConfig()
100208
}
101209
}

0 commit comments

Comments
 (0)