Skip to content

Commit 8c921b9

Browse files
committed
feature: code cleanup
1 parent 40e9389 commit 8c921b9

2 files changed

Lines changed: 80 additions & 222 deletions

File tree

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ android {
1212
minSdk = 30
1313
targetSdk = 36
1414
versionCode = 1
15-
versionName = "0.2"
15+
versionName = "0.3"
1616
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1717
}
1818

app/src/main/java/com/yureitzk/nophotopickerapi/MainHook.kt

Lines changed: 79 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.yureitzk.nophotopickerapi
33
import android.app.Activity
44
import android.content.Intent
55
import android.os.Build
6+
import android.os.ext.SdkExtensions
67
import android.provider.MediaStore
78
import de.robv.android.xposed.*
89
import de.robv.android.xposed.callbacks.XC_LoadPackage
@@ -11,227 +12,67 @@ class MainHook : IXposedHookLoadPackage {
1112

1213
companion object {
1314
private const val TAG = "NoPhotoPicker"
14-
private var isSystemFramework = false
1515
}
1616

17-
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
18-
when (lpparam.packageName) {
19-
"android" -> {
20-
isSystemFramework = true
21-
XposedBridge.log("$TAG: Hooking system framework")
17+
fun XC_LoadPackage.LoadPackageParam.isSystemFramework(): Boolean {
18+
return packageName == "android" || appInfo == null
19+
}
2220

23-
// Try multiple system hooks for better compatibility
21+
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
22+
when {
23+
lpparam.isSystemFramework() -> {
2424
hookSystemServices(lpparam)
25-
XposedBridge.log("$TAG: System framework hooks completed")
2625
}
27-
else -> {
28-
// App-specific hooks for compatibility
29-
if (!isSystemFramework) {
30-
hookActivity(lpparam)
31-
hookActivityResult(lpparam)
32-
}
26+
lpparam.packageName != null -> {
27+
hookActivity(lpparam)
28+
hookActivityResult(lpparam)
3329
}
3430
}
3531
}
3632

3733
private fun hookSystemServices(lpparam: XC_LoadPackage.LoadPackageParam) {
38-
// Try multiple system service hooks for different Android versions
39-
40-
// Hook ActivityTaskManagerService
41-
try {
42-
hookActivityTaskManager(lpparam)
43-
} catch (t: Throwable) {
44-
XposedBridge.log("$TAG: Failed to hook ActivityTaskManagerService: ${t.message}")
45-
}
46-
47-
// Hook ActivityManagerService
48-
try {
49-
hookActivityManagerService(lpparam)
50-
} catch (t: Throwable) {
51-
XposedBridge.log("$TAG: Failed to hook ActivityManagerService: ${t.message}")
52-
}
53-
54-
// Hook ActivityStarter
55-
try {
56-
hookActivityStarter(lpparam)
57-
} catch (t: Throwable) {
58-
XposedBridge.log("$TAG: Failed to hook ActivityStarter: ${t.message}")
59-
}
60-
61-
// Hook PackageManagerService
62-
try {
63-
hookPackageManagerService(lpparam)
64-
} catch (t: Throwable) {
65-
XposedBridge.log("$TAG: Failed to hook PackageManagerService: ${t.message}")
66-
}
67-
}
68-
69-
private fun hookActivityTaskManager(lpparam: XC_LoadPackage.LoadPackageParam) {
70-
try {
71-
val classLoader = lpparam.classLoader
72-
73-
// Try different class names for different Android versions
74-
val activityTaskManagerClass = XposedHelpers.findClassIfExists(
75-
"com.android.server.wm.ActivityTaskManagerService",
76-
classLoader
77-
) ?: XposedHelpers.findClassIfExists(
78-
"android.app.ActivityTaskManager",
79-
classLoader
80-
) ?: return
81-
82-
XposedBridge.hookAllMethods(
83-
activityTaskManagerClass,
84-
"startActivity",
85-
object : XC_MethodHook() {
86-
override fun beforeHookedMethod(param: MethodHookParam) {
87-
try {
88-
// Find Intent in arguments (position varies by API)
89-
for (i in param.args.indices) {
90-
if (param.args[i] is Intent) {
91-
val intent = param.args[i] as Intent
92-
if (isPhotoPickerIntent(intent)) {
93-
logIntentDetails(intent, "ActivityTaskManagerService.startActivity")
94-
param.args[i] = buildDocumentPickerIntent(intent)
95-
return
96-
}
97-
}
98-
}
99-
} catch (e: Throwable) {
100-
XposedBridge.log("$TAG: Error in ActivityTaskManagerService hook: ${e.message}")
101-
}
102-
}
103-
}
104-
)
105-
106-
XposedBridge.log("$TAG: Successfully hooked ActivityTaskManagerService")
107-
} catch (t: Throwable) {
108-
throw t
109-
}
110-
}
111-
112-
private fun hookActivityManagerService(lpparam: XC_LoadPackage.LoadPackageParam) {
113-
try {
114-
val classLoader = lpparam.classLoader
115-
val activityManagerClass = XposedHelpers.findClassIfExists(
116-
"com.android.server.am.ActivityManagerService",
117-
classLoader
118-
) ?: return
119-
120-
XposedBridge.hookAllMethods(
121-
activityManagerClass,
122-
"startActivity",
123-
object : XC_MethodHook() {
124-
override fun beforeHookedMethod(param: MethodHookParam) {
125-
try {
126-
val args = param.args
127-
for (i in args.indices) {
128-
if (args[i] is Intent) {
129-
val intent = args[i] as Intent
130-
if (isPhotoPickerIntent(intent)) {
131-
logIntentDetails(intent, "ActivityManagerService.startActivity")
132-
args[i] = buildDocumentPickerIntent(intent)
133-
return
134-
}
135-
}
136-
}
137-
} catch (e: Throwable) {
138-
XposedBridge.log("$TAG: Error in ActivityManagerService hook: ${e.message}")
139-
}
140-
}
141-
}
142-
)
143-
144-
XposedBridge.log("$TAG: Successfully hooked ActivityManagerService")
145-
} catch (t: Throwable) {
146-
throw t
34+
// Try to find which service class exists
35+
val classLoader = lpparam.classLoader
36+
val serviceClasses = listOf(
37+
"com.android.server.wm.ActivityTaskManagerService",
38+
"com.android.server.am.ActivityManagerService",
39+
"com.android.server.am.ActivityStarter"
40+
)
41+
42+
for (className in serviceClasses) {
43+
val serviceClass = XposedHelpers.findClassIfExists(className, classLoader)
44+
if (serviceClass != null) {
45+
XposedBridge.hookAllMethods(
46+
serviceClass,
47+
"startActivity",
48+
createIntentInterceptor("System:$className")
49+
)
50+
XposedBridge.log("$TAG: Hooked $className")
51+
return
52+
}
14753
}
14854
}
14955

150-
private fun hookActivityStarter(lpparam: XC_LoadPackage.LoadPackageParam) {
151-
try {
152-
val classLoader = lpparam.classLoader
153-
val activityStarterClass = XposedHelpers.findClassIfExists(
154-
"com.android.server.wm.ActivityStarter",
155-
classLoader
156-
) ?: return
157-
158-
XposedBridge.hookAllMethods(
159-
activityStarterClass,
160-
"execute",
161-
object : XC_MethodHook() {
162-
override fun beforeHookedMethod(param: MethodHookParam) {
163-
try {
164-
// ActivityStarter holds intent in mRequest field
165-
val intentField = XposedHelpers.getObjectField(param.thisObject, "mIntent")
166-
if (intentField is Intent) {
167-
if (isPhotoPickerIntent(intentField)) {
168-
logIntentDetails(intentField, "ActivityStarter.execute")
169-
XposedHelpers.setObjectField(
170-
param.thisObject,
171-
"mIntent",
172-
buildDocumentPickerIntent(intentField)
173-
)
174-
}
175-
}
176-
} catch (e: Throwable) {
177-
// Ignore - field might not exist or have different name
56+
private fun createIntentInterceptor(source: String): XC_MethodHook {
57+
return object : XC_MethodHook() {
58+
override fun beforeHookedMethod(param: MethodHookParam) {
59+
val args = param.args
60+
for (i in args.indices) {
61+
if (args[i] is Intent) {
62+
val intent = args[i] as Intent
63+
if (isPhotoPickerIntent(intent)) {
64+
logIntentDetails(intent, "$source.startActivity")
65+
args[i] = buildDocumentPickerIntent(intent)
66+
return
17867
}
17968
}
18069
}
181-
)
182-
183-
XposedBridge.log("$TAG: Successfully hooked ActivityStarter")
184-
} catch (t: Throwable) {
185-
throw t
186-
}
187-
}
188-
189-
private fun hookPackageManagerService(lpparam: XC_LoadPackage.LoadPackageParam) {
190-
try {
191-
val classLoader = lpparam.classLoader
192-
193-
// Try different class names
194-
val pmsClassNames = arrayOf(
195-
"com.android.server.pm.PackageManagerService",
196-
"com.android.server.pm.PackageManagerService\$IPackageManagerImpl",
197-
"android.content.pm.IPackageManager"
198-
)
199-
200-
var pmsClass: Class<*>? = null
201-
for (className in pmsClassNames) {
202-
pmsClass = XposedHelpers.findClassIfExists(className, classLoader)
203-
if (pmsClass != null) break
20470
}
205-
206-
if (pmsClass != null) {
207-
XposedBridge.hookAllMethods(
208-
pmsClass,
209-
"resolveIntent",
210-
object : XC_MethodHook() {
211-
override fun beforeHookedMethod(param: MethodHookParam) {
212-
try {
213-
val intent = param.args.getOrNull(0) as? Intent ?: return
214-
if (isPhotoPickerIntent(intent)) {
215-
logIntentDetails(intent, "PackageManagerService.resolveIntent")
216-
param.args[0] = buildDocumentPickerIntent(intent)
217-
}
218-
} catch (e: Throwable) {
219-
// Ignore
220-
}
221-
}
222-
}
223-
)
224-
225-
XposedBridge.log("$TAG: Successfully hooked PackageManagerService")
226-
}
227-
} catch (t: Throwable) {
228-
throw t
22971
}
23072
}
23173

23274
private fun hookActivity(lpparam: XC_LoadPackage.LoadPackageParam) {
23375
try {
234-
// Hook Activity.startActivity
23576
XposedHelpers.findAndHookMethod(
23677
Activity::class.java,
23778
"startActivity",
@@ -247,7 +88,6 @@ class MainHook : IXposedHookLoadPackage {
24788
}
24889
)
24990

250-
// Hook Activity.startActivityForResult
25191
XposedHelpers.findAndHookMethod(
25292
Activity::class.java,
25393
"startActivityForResult",
@@ -280,20 +120,27 @@ class MainHook : IXposedHookLoadPackage {
280120
Intent::class.java,
281121
object : XC_MethodHook() {
282122
override fun beforeHookedMethod(param: MethodHookParam) {
123+
val requestCode = param.args[0] as Int
283124
val resultCode = param.args[1] as Int
284125
val data = param.args[2] as? Intent
285126

286-
if (resultCode == Activity.RESULT_OK && data != null) {
287-
val hasData = data.data != null
288-
val hasClipData = data.clipData != null
127+
// Skip if canceled or no data
128+
if (resultCode != Activity.RESULT_OK || data == null) return
129+
130+
val hasContent = when {
131+
data.data != null -> true
132+
data.clipData?.let { clipData ->
133+
(0 until clipData.itemCount).any { clipData.getItemAt(it).uri != null }
134+
} ?: false -> true
135+
data.hasExtra(Intent.EXTRA_STREAM) -> true
136+
data.hasExtra(Intent.EXTRA_CONTENT_ANNOTATIONS) -> true
137+
else -> false
138+
}
289139

290-
if (!hasData && !hasClipData) {
291-
XposedBridge.log("$TAG: Empty result detected, changing to RESULT_CANCELED")
292-
param.args[1] = Activity.RESULT_CANCELED
293-
param.args[2] = null
294-
} else {
295-
XposedBridge.log("$TAG: Valid result - hasData: $hasData, hasClipData: $hasClipData")
296-
}
140+
if (!hasContent) {
141+
XposedBridge.log("$TAG: Empty result detected for request $requestCode")
142+
param.args[1] = Activity.RESULT_CANCELED
143+
param.args[2] = null
297144
}
298145
}
299146
}
@@ -303,19 +150,32 @@ class MainHook : IXposedHookLoadPackage {
303150
}
304151
}
305152

153+
private fun getMaxItems(intent: Intent): Int {
154+
return if (
155+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ||
156+
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2
157+
) {
158+
intent.getIntExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1)
159+
} else {
160+
-1
161+
}
162+
}
163+
306164
private fun isPhotoPickerIntent(intent: Intent): Boolean {
307-
return intent.action == MediaStore.ACTION_PICK_IMAGES ||
308-
intent.action == "androidx.activity.result.contract.action.PickVisualMedia" ||
309-
intent.component?.className?.contains("PhotoPicker", true) == true ||
310-
intent.component?.className?.contains("MediaPicker", true) == true ||
311-
intent.hasExtra("android.provider.extra.PICK_IMAGES") ||
312-
intent.hasExtra("androidx.activity.result.contract.extra.PICK_VISUAL_MEDIA_ENABLE_PHOTO_PICKER")
165+
return when {
166+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ->
167+
intent.action == MediaStore.ACTION_PICK_IMAGES
168+
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2 ->
169+
intent.action == MediaStore.ACTION_PICK_IMAGES ||
170+
intent.action == "androidx.activity.result.contract.action.PickVisualMedia"
171+
172+
else -> false
173+
}
313174
}
314175

315176
private fun logIntentDetails(intent: Intent, source: String) {
316177
XposedBridge.log("$TAG: [$source] Photo picker detected")
317178
XposedBridge.log(" Action: ${intent.action}")
318-
XposedBridge.log(" Component: ${intent.component}")
319179
}
320180

321181
private fun buildDocumentPickerIntent(original: Intent): Intent {
@@ -335,13 +195,11 @@ class MainHook : IXposedHookLoadPackage {
335195

336196
// Handle multi-select with API compatibility
337197
val allowMultiple = original.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
338-
val maxItems = original.getIntExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1)
198+
val maxItems = getMaxItems(original)
339199

340200
val shouldAllowMultiple = when {
341201
allowMultiple -> true
342202
maxItems > 1 -> true
343-
// API 33+ check without calling the method on older APIs
344-
Build.VERSION.SDK_INT >= 33 && maxItems >= 2 -> true
345203
else -> false
346204
}
347205

@@ -354,4 +212,4 @@ class MainHook : IXposedHookLoadPackage {
354212
XposedBridge.log("$TAG: Created document picker intent")
355213
}
356214
}
357-
}
215+
}

0 commit comments

Comments
 (0)