feat: Implement legacy storage permission handling for Android 10 and below, and improve compatibility by fixing service hook and attribution source issues.

This commit is contained in:
alex5402
2026-01-31 11:48:11 +05:30
parent 6ad99e0edd
commit 3f8ede8968
6 changed files with 76 additions and 6 deletions
@@ -50,6 +50,10 @@ public abstract class ClassInvocationStub implements InvocationHandler, IInjectH
@Override
public void injectHook() {
mBase = getWho();
// Skip hook if service doesn't exist on this Android version
if (mBase == null) {
return;
}
mProxyInvocation = Proxy.newProxyInstance(mBase.getClass().getClassLoader(), MethodParameterUtils.getAllInterface(mBase.getClass()), this);
if (!onlyProxy) {
inject(mBase, mProxyInvocation);
@@ -91,7 +91,7 @@ import top.niunaijun.blackbox.utils.compat.BuildCompat;
import top.niunaijun.blackbox.fake.service.ISettingsProviderProxy;
import top.niunaijun.blackbox.fake.service.FeatureFlagUtilsProxy;
import top.niunaijun.blackbox.fake.service.WorkManagerProxy;
import top.niunaijun.blackbox.fake.service.IInputMethodManagerProxy;
// Removed IInputMethodManagerProxy - causes ClassCastException on Android 15
/**
* updated by alex5402 on 3/30/21.
@@ -177,7 +177,7 @@ public class HookManager {
addInjector(new IMediaRouterServiceProxy());
addInjector(new IPowerManagerProxy());
addInjector(new IContextHubServiceProxy());
addInjector(new IInputMethodManagerProxy());
// Removed IInputMethodManagerProxy - causes ClassCastException on Android 15 due to queryLocalInterface returning non-IInterface proxy
addInjector(new IVibratorServiceProxy());
addInjector(new IPersistentDataBlockServiceProxy());
addInjector(AppInstrumentation.get());
@@ -60,9 +60,12 @@ public class SystemProviderStub extends ClassInvocationStub implements BContentP
if ("call".equals(methodName)) {
// Only fix AttributionSource in call() args, don't replace method name
if (args != null) {
Class<?> attributionSourceClass = BRAttributionSource.getRealClass();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg != null && arg.getClass().getName().equals(BRAttributionSource.getRealClass().getName())) {
// Check for null - AttributionSource doesn't exist on Android < S (31)
if (arg != null && attributionSourceClass != null &&
arg.getClass().getName().equals(attributionSourceClass.getName())) {
ContextCompat.fixAttributionSourceState(arg, BlackBoxCore.getHostUid());
}
}
@@ -79,8 +82,12 @@ public class SystemProviderStub extends ClassInvocationStub implements BContentP
if (!isSystemProviderAuthority(authority)) {
args[0] = BlackBoxCore.getHostPkg();
}
} else if (arg != null && arg.getClass().getName().equals(BRAttributionSource.getRealClass().getName())) {
ContextCompat.fixAttributionSourceState(arg, BlackBoxCore.getHostUid());
} else if (arg != null) {
Class<?> attrSourceClass = BRAttributionSource.getRealClass();
// Check for null - AttributionSource doesn't exist on Android < S (31)
if (attrSourceClass != null && arg.getClass().getName().equals(attrSourceClass.getName())) {
ContextCompat.fixAttributionSourceState(arg, BlackBoxCore.getHostUid());
}
}
}
return method.invoke(mBase, args);
+6
View File
@@ -6,6 +6,12 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<!-- Storage permissions for Android 10 and below -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<application
android:name=".app.App"
android:allowBackup="true"
@@ -37,6 +37,7 @@ class MainActivity : LoadingActivity() {
companion object {
private const val TAG = "MainActivity"
private const val STORAGE_PERMISSION_REQUEST_CODE = 1001
fun start(context: Context) {
val intent = Intent(context, MainActivity::class.java)
@@ -78,16 +79,68 @@ class MainActivity : LoadingActivity() {
private fun checkStoragePermission() {
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
// Android 11+: Need MANAGE_EXTERNAL_STORAGE
if (!android.os.Environment.isExternalStorageManager()) {
Log.w(TAG, "MANAGE_EXTERNAL_STORAGE permission not granted")
showStoragePermissionDialog()
}
} else {
// Android 10 and below: Need READ/WRITE_EXTERNAL_STORAGE
if (androidx.core.content.ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
) != android.content.pm.PackageManager.PERMISSION_GRANTED ||
androidx.core.content.ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_EXTERNAL_STORAGE
) != android.content.pm.PackageManager.PERMISSION_GRANTED
) {
Log.w(
TAG,
"Storage permissions not granted on Android ${android.os.Build.VERSION.SDK_INT}"
)
requestLegacyStoragePermission()
}
}
} catch (e: Exception) {
Log.e(TAG, "Error checking storage permission: ${e.message}")
}
}
private fun requestLegacyStoragePermission() {
try {
androidx.core.app.ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
),
STORAGE_PERMISSION_REQUEST_CODE
)
} catch (e: Exception) {
Log.e(TAG, "Error requesting storage permission: ${e.message}")
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() &&
grantResults.all {
it == android.content.pm.PackageManager.PERMISSION_GRANTED
}
) {
Log.d(TAG, "Storage permissions granted")
} else {
Log.w(TAG, "Storage permissions denied")
}
}
}
private fun showStoragePermissionDialog() {
try {
MaterialDialog(this).show {
+1 -1
View File
@@ -11,7 +11,7 @@ ext {
// targetSdkVersion = 35
minSdk = 21
versionCode = 1
versionName = "3.0.13-beta2"
versionName = "3.1.0-beta"
xVersion = '1.1.0'
hiddenApiBypass = '4.3'
}