Skip to content

Commit fd8b1d9

Browse files
committed
fix: improve intent handling for download and market activities
1 parent 0fbfab8 commit fd8b1d9

2 files changed

Lines changed: 134 additions & 107 deletions

File tree

library/libhook/src/main/java/com/sevtinge/hyperceiler/libhook/rules/systemframework/others/BypassForceDownloadui.java

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
import com.sevtinge.hyperceiler.libhook.base.BaseHook;
2626
import com.sevtinge.hyperceiler.libhook.callback.IMethodHook;
2727

28-
import java.lang.reflect.Method;
28+
import java.util.List;
2929

3030
import io.github.kyuubiran.ezxhelper.xposed.common.HookParam;
31+
import io.github.libxposed.api.XposedInterface;
3132

3233
/**
3334
* 修复从快速分享查看下载的内容时跳转错误
@@ -36,54 +37,46 @@
3637
*/
3738
public class BypassForceDownloadui extends BaseHook {
3839

40+
private static final String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
41+
3942
@Override
4043
public void init() {
4144

4245
try {
43-
Class<?> cls =
44-
findClass("com.android.server.wm.ActivityStartController");
45-
46-
for (Method method : cls.getDeclaredMethods()) {
47-
if (!method.getName().equals("startActivityInPackage")) continue;
48-
//if (!Modifier.isPublic(method.getModifiers())) continue;
49-
50-
Class<?>[] paramTypes = method.getParameterTypes();
51-
int intentIndex = -1;
52-
for (int i = 0; i < paramTypes.length; i++) {
53-
if (Intent.class.equals(paramTypes[i])) {
54-
intentIndex = i;
55-
break;
56-
}
57-
}
58-
59-
if (intentIndex == -1) continue;
60-
61-
final int index = intentIndex;
62-
hookMethod(method, new IMethodHook() {
63-
@Override
64-
public void before(HookParam param) {
65-
try {
66-
Intent intent = (Intent) param.getArgs()[index];
67-
if (intent == null) return;
68-
69-
// Uri data = intent.getData(); // ?
70-
71-
if (!"android.intent.action.VIEW_DOWNLOADS".equals(intent.getAction()))
72-
return;
73-
74-
intent.setPackage(null); // 移除指定包名,如果不移除 documentsui 也会强制跳到 downloads.ui
75-
Intent chooser = Intent.createChooser(intent, null);
76-
param.getArgs()[index] = chooser;
77-
78-
XposedLog.d(TAG, getPackageName(), "Forced chooser for android.intent.action.VIEW_DOWNLOADS");
79-
} catch (Throwable t) {
80-
XposedLog.w(TAG, getPackageName(), "Error - " + Log.getStackTraceString(t));
46+
Class<?> ascClass = findClassIfExists("com.android.server.wm.ActivityStartController");
47+
if (ascClass == null) {
48+
XposedLog.w(TAG, getPackageName(), "Class not found: com.android.server.wm.ActivityStartController");
49+
return;
50+
}
8151

82-
}
52+
List<XposedInterface.HookHandle> handles = hookAllMethods(ascClass, "startActivityInPackage", new IMethodHook() {
53+
@Override
54+
public void before(HookParam param) {
55+
try {
56+
Object[] args = param.getArgs();
57+
int intentIndex = findIntentArgIndex(args);
58+
if (intentIndex < 0) return;
59+
60+
Intent intent = (Intent) args[intentIndex];
61+
if (intent == null) return;
62+
63+
// 注意:该 hook 同样处于系统层 startActivity 拦截链路,范围较大,必须严格限制到 VIEW_DOWNLOADS 场景。
64+
// 这样可以降低与应用商店相关 hook 在同一调用链上互相影响的概率。
65+
if (Intent.ACTION_CHOOSER.equals(intent.getAction())) return;
66+
if (!ACTION_VIEW_DOWNLOADS.equals(intent.getAction())) return;
67+
68+
intent.setPackage(null); // 移除指定包名,如果不移除 documentsui 也会强制跳到 downloads.ui
69+
args[intentIndex] = Intent.createChooser(intent, null);
70+
XposedLog.d(TAG, getPackageName(), "Forced chooser for android.intent.action.VIEW_DOWNLOADS");
71+
} catch (Throwable t) {
72+
XposedLog.w(TAG, getPackageName(), "Error - " + Log.getStackTraceString(t));
8373
}
84-
});
85-
XposedLog.d(TAG, getPackageName(), "Hooked method: " + method);
86-
break;
74+
}
75+
});
76+
if (handles == null || handles.isEmpty()) {
77+
XposedLog.w(TAG, getPackageName(), "No startActivityInPackage overload hooked");
78+
} else {
79+
XposedLog.d(TAG, getPackageName(), "Hooked startActivityInPackage overloads: " + handles.size());
8780
}
8881

8982
} catch (Throwable t) {
@@ -92,4 +85,12 @@ public void before(HookParam param) {
9285
}
9386

9487
}
88+
89+
private static int findIntentArgIndex(Object[] args) {
90+
if (args == null) return -1;
91+
for (int i = 0; i < args.length; i++) {
92+
if (args[i] instanceof Intent) return i;
93+
}
94+
return -1;
95+
}
9596
}

library/libhook/src/main/java/com/sevtinge/hyperceiler/libhook/rules/systemframework/others/BypassForceMiAppStore.java

Lines changed: 90 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
import com.sevtinge.hyperceiler.libhook.base.BaseHook;
3030
import com.sevtinge.hyperceiler.libhook.callback.IMethodHook;
3131

32-
import java.lang.reflect.Method;
33-
import java.lang.reflect.Modifier;
32+
import java.util.List;
3433

3534
import io.github.kyuubiran.ezxhelper.xposed.common.HookParam;
35+
import io.github.libxposed.api.XposedInterface;
3636

3737
/**
3838
* 绕过打开应用商店时强制使用小米应用商店
@@ -41,93 +41,119 @@
4141
*/
4242
public class BypassForceMiAppStore extends BaseHook {
4343

44+
private static final String PREF_FORCE_CHOOSER = "system_framework_bypass_force_mi_appstore";
45+
private static final String PREF_DETAIL_MINI = "system_framework_market_use_detailmini";
46+
4447
@Override
4548
public void init() {
4649

4750
try {
48-
Class<?> cls =
49-
findClass("com.android.server.wm.ActivityTaskManagerService");
50-
51-
for (Method method : cls.getDeclaredMethods()) {
52-
if (!method.getName().equals("startActivity")) continue;
53-
if (!Modifier.isPublic(method.getModifiers())) continue;
54-
55-
Class<?>[] paramTypes = method.getParameterTypes();
56-
int intentIndex = -1;
57-
for (int i = 0; i < paramTypes.length; i++) {
58-
if (Intent.class.equals(paramTypes[i])) {
59-
intentIndex = i;
60-
break;
61-
}
62-
}
63-
64-
if (intentIndex == -1) continue;
65-
66-
final int index = intentIndex;
67-
hookMethod(method, new IMethodHook() {
68-
@Override
69-
public void before(HookParam param) {
70-
try {
71-
Intent intent = (Intent) param.getArgs()[index];
72-
if (intent == null) return;
73-
74-
Uri data = intent.getData();
75-
if (!Intent.ACTION_VIEW.equals(intent.getAction()) || data == null)
76-
return;
51+
Class<?> atmsClass = findClassIfExists("com.android.server.wm.ActivityTaskManagerService");
52+
if (atmsClass == null) {
53+
XposedLog.w(TAG, getPackageName(), "Class not found: com.android.server.wm.ActivityTaskManagerService");
54+
return;
55+
}
7756

78-
String uriStr = data.toString();
57+
List<XposedInterface.HookHandle> handles = hookAllMethods(atmsClass, "startActivity", new IMethodHook() {
58+
@Override
59+
public void before(HookParam param) {
60+
try {
61+
Object[] args = param.getArgs();
62+
int intentIndex = findIntentArgIndex(args);
63+
if (intentIndex < 0) return;
7964

80-
if (uriStr != null) {
81-
// 始终替换 mimarket:// 为 market://
82-
uriStr = uriStr.replaceFirst("^mimarket://", "market://");
65+
Intent intent = (Intent) args[intentIndex];
66+
if (intent == null || !Intent.ACTION_VIEW.equals(intent.getAction())) return;
8367

84-
// 如果启用了 detailmini 选项,则替换 path 中的 details → details/detailmini
85-
if (PrefsBridge.getBoolean("system_framework_market_use_detailmini")) {
86-
uriStr = uriStr.replaceFirst("(?<=market://)(details)(?!/detailmini)", "details/detailmini");
87-
}
68+
final boolean forceChooserEnabled = PrefsBridge.getBoolean(PREF_FORCE_CHOOSER);
69+
final boolean detailMiniEnabled = PrefsBridge.getBoolean(PREF_DETAIL_MINI);
70+
if (!forceChooserEnabled && !detailMiniEnabled) return;
8871

89-
intent.setData(Uri.parse(uriStr));
90-
}
72+
Uri data = intent.getData();
73+
if (data == null) return;
9174

92-
// 如果是 market://details?id=...
93-
if ("market".equals(intent.getData().getScheme())
94-
&& ("details".equals(intent.getData().getHost()) || "details/detailmini".equals(intent.getData().getHost()))
95-
&& intent.getData().getQueryParameter("id") != null
96-
) {
75+
Uri newData = data;
76+
String type = intent.getType();
9777

98-
// 移除指定包名
99-
if (intent.getPackage() != null) {
100-
XposedLog.d(TAG, getPackageName(), "Removed package=:" + intent.getPackage());
101-
intent.setPackage(null);
102-
}
78+
// 注意:该 hook 属于系统层 startActivity 拦截,开关间存在联动影响,必须将改写范围收窄到 market 相关 URI。
79+
if ("mimarket".equals(newData.getScheme())) {
80+
newData = newData.buildUpon().scheme("market").build();
81+
}
10382

104-
//FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 会导致小米应用商店无法打开,原因未知
105-
intent.removeFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
83+
// 如果启用了 detailmini,则将 market://details?id=... 改写为 market://details/detailmini?id=...
84+
String path = newData.getPath();
85+
if (detailMiniEnabled
86+
&& isMarketDetailsHost(newData)
87+
&& (path == null || path.isEmpty() || "/".equals(path))) {
88+
newData = newData.buildUpon().path("/detailmini").build();
89+
}
10690

107-
// 如果启用了 bypass_force_mi_appstore,则强制使用 chooser 打开应用商店
108-
if (PrefsBridge.getBoolean("system_framework_bypass_force_mi_appstore")) {
109-
intent = Intent.createChooser(intent, null);
91+
// setData(...) 会清空 type,使用 setDataAndType(...) 保留 MIME type。
92+
if (!newData.equals(data)) {
93+
if (type != null) {
94+
intent.setDataAndType(newData, type);
95+
} else {
96+
intent.setData(newData);
97+
}
98+
}
11099

111-
XposedLog.d(TAG, getPackageName(), "Forced chooser for market://details intent");
112-
}
100+
Uri finalData = intent.getData();
101+
if (finalData == null) return;
113102

103+
// 只处理 market://details[(/detailmini)]?id=...,避免影响其他 ACTION_VIEW intent。
104+
if (isMarketDetailsIntent(finalData) && finalData.getQueryParameter("id") != null) {
105+
// 移除指定包名
106+
if (intent.getPackage() != null) {
107+
XposedLog.d(TAG, getPackageName(), "Removed package=:" + intent.getPackage());
108+
intent.setPackage(null);
114109
}
115-
param.getArgs()[index] = intent;
116110

117-
} catch (Throwable t) {
118-
XposedLog.w(TAG, getPackageName(), "Error - " + Log.getStackTraceString(t));
111+
// FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 会导致小米应用商店无法打开,原因未知
112+
intent.removeFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
119113

114+
// 如果启用了 bypass_force_mi_appstore,则强制使用 chooser 打开应用商店
115+
if (forceChooserEnabled) {
116+
intent = Intent.createChooser(intent, null);
117+
XposedLog.d(TAG, getPackageName(), "Forced chooser for market://details intent");
118+
}
120119
}
120+
121+
args[intentIndex] = intent;
122+
} catch (Throwable t) {
123+
XposedLog.w(TAG, getPackageName(), "Error - " + Log.getStackTraceString(t));
121124
}
122-
});
123-
XposedLog.d(TAG, getPackageName(), "Hooked method: " + method);
124-
break;
125+
}
126+
});
127+
if (handles == null || handles.isEmpty()) {
128+
XposedLog.w(TAG, getPackageName(), "No startActivity overload hooked");
129+
} else {
130+
XposedLog.d(TAG, getPackageName(), "Hooked startActivity overloads: " + handles.size());
125131
}
126132

127133
} catch (Throwable t) {
128134
XposedLog.w(TAG, getPackageName(), "Failed to hook - " + Log.getStackTraceString(t));
129135
}
130136
}
131137

138+
private static int findIntentArgIndex(Object[] args) {
139+
if (args == null) return -1;
140+
for (int i = 0; i < args.length; i++) {
141+
if (args[i] instanceof Intent) return i;
142+
}
143+
return -1;
144+
}
145+
146+
private static boolean isMarketDetailsHost(Uri uri) {
147+
return uri != null
148+
&& "market".equals(uri.getScheme())
149+
&& "details".equals(uri.getHost());
150+
}
151+
152+
private static boolean isMarketDetailsIntent(Uri uri) {
153+
if (!isMarketDetailsHost(uri)) return false;
154+
155+
String path = uri.getPath();
156+
return path == null || path.isEmpty() || "/".equals(path) || "/detailmini".equals(path);
157+
}
132158

133159
}

0 commit comments

Comments
 (0)