Skip to content

Commit 4d54fc7

Browse files
committed
fix: avoid breaking new rear screen apply when allowing third-party themes
1 parent 2b513de commit 4d54fc7

3 files changed

Lines changed: 175 additions & 6 deletions

File tree

library/libhook/src/main/java/com/sevtinge/hyperceiler/libhook/rules/systemframework/display/ThemeProvider.kt

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,54 @@ package com.sevtinge.hyperceiler.libhook.rules.systemframework.display
2020

2121
import com.sevtinge.hyperceiler.common.log.XposedLog
2222
import com.sevtinge.hyperceiler.libhook.base.BaseHook
23+
import com.sevtinge.hyperceiler.libhook.utils.api.ContextUtils
24+
import com.sevtinge.hyperceiler.libhook.utils.guard.RearScreenFlowGuard
2325
import io.github.kyuubiran.ezxhelper.core.finder.MethodFinder.`-Static`.methodFinder
2426
import io.github.kyuubiran.ezxhelper.xposed.dsl.HookFactory.`-Static`.createHook
2527
import io.github.kyuubiran.ezxhelper.xposed.dsl.HookFactory.`-Static`.createHooks
2628
import io.github.libxposed.api.XposedInterface
2729
import miui.drm.DrmManager
2830
import miui.drm.ThemeReceiver
31+
import java.util.ArrayDeque
2932

3033
class ThemeProvider : BaseHook() {
34+
// validateTheme may nest on one thread, so temporary hooks need to be unwound by call depth.
35+
private val hookStack = ThreadLocal.withInitial<ArrayDeque<List<XposedInterface.HookHandle>>> { ArrayDeque() }
36+
3137
override fun init() {
32-
var hook: List<XposedInterface.HookHandle>? = null
3338
try {
3439
ThemeReceiver::class.java.methodFinder().filterByName("validateTheme").first().createHook {
3540
before {
36-
hook = DrmManager::class.java.methodFinder().filterByName("isLegal").toList().createHooks {
37-
returnConstant(DrmManager.DrmResult.DRM_SUCCESS)
41+
val systemContext = ContextUtils.getContextNoError(ContextUtils.FlAG_ONLY_ANDROID)
42+
val hooks = if (RearScreenFlowGuard.isRearScreenActivityActive(systemContext)) {
43+
emptyList()
44+
} else {
45+
runCatching {
46+
DrmManager::class.java.methodFinder().filterByName("isLegal").toList().createHooks {
47+
returnConstant(DrmManager.DrmResult.DRM_SUCCESS)
48+
}
49+
}.onFailure { throwable ->
50+
XposedLog.e(TAG, packageName, throwable)
51+
}.getOrDefault(emptyList())
3852
}
53+
54+
currentHookStack().addLast(hooks)
3955
}
4056
after {
41-
hook?.forEach { it.unhook() }
57+
val stack = currentHookStack()
58+
if (!stack.isEmpty()) {
59+
stack.removeLast().forEach { it.unhook() }
60+
}
61+
if (stack.isEmpty()) {
62+
hookStack.remove()
63+
}
4264
}
4365
}
4466
} catch (t: Throwable) {
4567
XposedLog.e(TAG, packageName, t)
4668
}
4769
}
70+
71+
private fun currentHookStack(): ArrayDeque<List<XposedInterface.HookHandle>> =
72+
checkNotNull(hookStack.get())
4873
}

library/libhook/src/main/java/com/sevtinge/hyperceiler/libhook/rules/thememanager/AllowThirdTheme.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020

2121
import com.sevtinge.hyperceiler.libhook.base.BaseHook;
2222
import com.sevtinge.hyperceiler.libhook.callback.IMethodHook;
23+
import com.sevtinge.hyperceiler.libhook.utils.api.ContextUtils;
2324
import com.sevtinge.hyperceiler.libhook.utils.hookapi.dexkit.DexKit;
2425
import com.sevtinge.hyperceiler.libhook.utils.hookapi.dexkit.IDexKit;
26+
import com.sevtinge.hyperceiler.libhook.utils.guard.RearScreenFlowGuard;
2527

2628
import org.luckypray.dexkit.DexKitBridge;
2729
import org.luckypray.dexkit.query.FindMethod;
@@ -34,11 +36,11 @@
3436
import io.github.kyuubiran.ezxhelper.xposed.common.HookParam;
3537
import miui.drm.DrmManager;
3638

37-
;
38-
3939
public class AllowThirdTheme extends BaseHook {
4040
@Override
4141
public void init() {
42+
runOnApplicationAttach(RearScreenFlowGuard::ensureActivityTrackerRegistered);
43+
4244
Method method = DexKit.findMember("CheckRightsIsLegal", new IDexKit() {
4345
@Override
4446
public BaseData dexkit(DexKitBridge bridge) throws ReflectiveOperationException {
@@ -52,6 +54,11 @@ public BaseData dexkit(DexKitBridge bridge) throws ReflectiveOperationException
5254
hookMethod(method, new IMethodHook() {
5355
@Override
5456
public void before(HookParam param) {
57+
if (RearScreenFlowGuard.isRearScreenActivityActive(
58+
ContextUtils.getContextNoError(ContextUtils.FLAG_CURRENT_APP)
59+
)) {
60+
return;
61+
}
5562
param.setResult(DrmManager.DrmResult.DRM_SUCCESS);
5663
}
5764
});
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* This file is part of HyperCeiler.
3+
*
4+
* HyperCeiler is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as
6+
* published by the Free Software Foundation, either version 3 of the
7+
* License.
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 Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*
17+
* Copyright (C) 2023-2026 HyperCeiler Contributions
18+
*/
19+
package com.sevtinge.hyperceiler.libhook.utils.guard;
20+
21+
import android.app.Activity;
22+
import android.app.ActivityManager;
23+
import android.app.Application;
24+
import android.content.ComponentName;
25+
import android.content.Context;
26+
import android.os.Bundle;
27+
28+
import java.util.Collections;
29+
import java.util.HashSet;
30+
import java.util.List;
31+
import java.util.Set;
32+
import java.util.concurrent.atomic.AtomicBoolean;
33+
34+
public final class RearScreenFlowGuard {
35+
private static final String REAR_SCREEN_DETAIL_ACTIVITY = "com.rearScreen.RearScreenDetailActivity";
36+
private static final AtomicBoolean sLifecycleRegistered = new AtomicBoolean(false);
37+
private static final Set<String> sActiveActivityClassNames =
38+
Collections.synchronizedSet(new HashSet<>());
39+
40+
private RearScreenFlowGuard() {
41+
}
42+
43+
public static void ensureActivityTrackerRegistered(Context context) {
44+
if (context == null) {
45+
return;
46+
}
47+
Context applicationContext = context.getApplicationContext();
48+
if (!(applicationContext instanceof Application application)) {
49+
return;
50+
}
51+
refreshCurrentActivity(applicationContext);
52+
if (!sLifecycleRegistered.compareAndSet(false, true)) {
53+
return;
54+
}
55+
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
56+
@Override
57+
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
58+
// No-op.
59+
}
60+
61+
@Override
62+
public void onActivityStarted(Activity activity) {
63+
sActiveActivityClassNames.add(activity.getClass().getName());
64+
}
65+
66+
@Override
67+
public void onActivityResumed(Activity activity) {
68+
sActiveActivityClassNames.add(activity.getClass().getName());
69+
}
70+
71+
@Override
72+
public void onActivityPaused(Activity activity) {
73+
// No-op.
74+
}
75+
76+
@Override
77+
public void onActivityStopped(Activity activity) {
78+
sActiveActivityClassNames.remove(activity.getClass().getName());
79+
}
80+
81+
@Override
82+
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
83+
// No-op.
84+
}
85+
86+
@Override
87+
public void onActivityDestroyed(Activity activity) {
88+
sActiveActivityClassNames.remove(activity.getClass().getName());
89+
}
90+
});
91+
}
92+
93+
public static boolean isRearScreenActivityActive() {
94+
return sActiveActivityClassNames.contains(REAR_SCREEN_DETAIL_ACTIVITY);
95+
}
96+
97+
public static boolean isRearScreenActivityActive(Context context) {
98+
if (isRearScreenActivityActive()) {
99+
return true;
100+
}
101+
if (context == null) {
102+
return false;
103+
}
104+
try {
105+
ActivityManager activityManager = context.getSystemService(ActivityManager.class);
106+
if (activityManager == null) {
107+
return false;
108+
}
109+
List<ActivityManager.RunningTaskInfo> runningTasks = activityManager.getRunningTasks(1);
110+
if (runningTasks == null || runningTasks.isEmpty()) {
111+
return false;
112+
}
113+
ComponentName topActivity = runningTasks.get(0).topActivity;
114+
return topActivity != null && REAR_SCREEN_DETAIL_ACTIVITY.equals(topActivity.getClassName());
115+
} catch (Throwable ignored) {
116+
return false;
117+
}
118+
}
119+
120+
private static void refreshCurrentActivity(Context context) {
121+
try {
122+
ActivityManager activityManager = context.getSystemService(ActivityManager.class);
123+
if (activityManager == null) {
124+
return;
125+
}
126+
List<ActivityManager.RunningTaskInfo> runningTasks = activityManager.getRunningTasks(1);
127+
if (runningTasks == null || runningTasks.isEmpty()) {
128+
return;
129+
}
130+
ComponentName topActivity = runningTasks.get(0).topActivity;
131+
if (topActivity != null) {
132+
sActiveActivityClassNames.add(topActivity.getClassName());
133+
}
134+
} catch (Throwable ignored) {
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)