diff --git a/README.md b/README.md index 9c24cec..adc39c4 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Extremely useful for app debugging. Scoop supports both rooted and non-rooted devices (though non-rooted devices require some [setup](https://github.com/TacoTheDank/Scoop#guide)). +Scoop also supports Xposed, but only the GitHub releases will do so. F-Droid releases will [NOT](https://github.com/TacoTheDank/Scoop/issues/8#issuecomment-827222534) support Xposed. +If you want to use Scoop with Xposed, you must use the release builds from the GitHub repository. Features: - Search (apps, stack traces) @@ -41,7 +43,6 @@ Most important differences with this fork: - Lots of under-the-hood improvements - Using topjohnwu's libsu - App now has an adaptive icon -- Removed Xposed support You can also read the release notes for changes that have been made to the app since then. diff --git a/app/build.gradle b/app/build.gradle index f9f9b3c..ed35663 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,6 +26,18 @@ android { preDexLibraries true } + flavorDimensions "dimension" + productFlavors { + // No Xposed support + fdroid { + dimension "dimension" + } + // Xposed support + github { + dimension "dimension" + } + } + buildTypes { release { minifyEnabled true @@ -59,6 +71,10 @@ android { } dependencies { +// Xposed (only for GitHub release builds) + githubCompileOnly files('libs/xposed-api-82.jar') + githubCompileOnly files('libs/xposed-api-82-sources.jar') + // Kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" diff --git a/app/libs/xposed-api-82-sources.jar b/app/libs/xposed-api-82-sources.jar new file mode 100644 index 0000000..6e3a0fb Binary files /dev/null and b/app/libs/xposed-api-82-sources.jar differ diff --git a/app/libs/xposed-api-82.jar b/app/libs/xposed-api-82.jar new file mode 100644 index 0000000..eec212f Binary files /dev/null and b/app/libs/xposed-api-82.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 048ad81..24eff8c 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,5 +18,9 @@ -dontobfuscate +-keep class tk.wasdennnoch.scoop.XposedHook +-keep class tk.wasdennnoch.scoop.MockThrowable { *; } -keep class androidx.appcompat.widget.SearchView { *; } -keep class tk.wasdennnoch.scoop.data.crash.Crash { *; } + +-keep class de.robv.android.xposed.** { *; } diff --git a/app/src/github/AndroidManifest.xml b/app/src/github/AndroidManifest.xml new file mode 100644 index 0000000..134f93d --- /dev/null +++ b/app/src/github/AndroidManifest.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="tk.wasdennnoch.scoop" + android:installLocation="internalOnly"> + + <uses-permission android:name="android.permission.VIBRATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission + android:name="android.permission.READ_LOGS" + tools:ignore="ProtectedPermissions" /> + + <application + android:name=".ScoopApplication" + android:allowBackup="true" + android:fullBackupContent="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <meta-data + android:name="xposedmodule" + android:value="true" /> + <meta-data + android:name="xposedminversion" + android:value="82" /> + <meta-data + android:name="preloaded_fonts" + android:resource="@array/preloaded_fonts" /> + + <activity + android:name=".ui.MainActivity" + android:configChanges="orientation|screenSize|screenLayout" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + <category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" /> + </intent-filter> + </activity> + <activity + android:name=".ui.DetailActivity" + android:parentActivityName=".ui.MainActivity" + tools:ignore="UnusedAttribute"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".ui.MainActivity" /> + </activity> + <activity + android:name=".ui.SettingsActivity" + android:label="@string/settings_title"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.NOTIFICATION_PREFERENCES" /> + </intent-filter> + </activity> + <activity + android:name=".ui.BlacklistAppsActivity" + android:label="@string/blacklist_title" /> + <activity + android:name=".ui.AboutActivity" + android:label="@string/about_title" /> + + <receiver + android:name=".receiver.CrashReceiver" + android:exported="true" + tools:ignore="ExportedReceiver"> + <intent-filter> + <action android:name="tk.wasdennnoch.scoop.EXCEPTION" /> + </intent-filter> + </receiver> + <receiver + android:name=".receiver.ShareReceiver" + android:exported="true" + tools:ignore="ExportedReceiver"> + <intent-filter> + <action android:name="tk.wasdennnoch.scoop.ACTION_SHARE" /> + <action android:name="tk.wasdennnoch.scoop.ACTION_COPY" /> + </intent-filter> + </receiver> + + <service + android:name=".IndicatorService" + android:enabled="true" /> + + <receiver + android:name=".receiver.StopReceiver" + android:enabled="true" /> + + <service + android:name=".detector.CrashDetectorService" + android:enabled="true" + android:process=":crashDetectorService" /> + <service + android:name=".dogbin.DogbinUploadService" + android:enabled="true" + android:exported="false" /> + </application> + +</manifest> diff --git a/app/src/github/assets/xposed_init b/app/src/github/assets/xposed_init new file mode 100644 index 0000000..68309e7 --- /dev/null +++ b/app/src/github/assets/xposed_init @@ -0,0 +1 @@ +tk.wasdennnoch.scoop.XposedHook diff --git a/app/src/github/java/tk/wasdennnoch/scoop/XposedHook.java b/app/src/github/java/tk/wasdennnoch/scoop/XposedHook.java new file mode 100644 index 0000000..304432c --- /dev/null +++ b/app/src/github/java/tk/wasdennnoch/scoop/XposedHook.java @@ -0,0 +1,111 @@ +package tk.wasdennnoch.scoop; + +import android.app.Application; +import android.content.Intent; +import android.util.Log; + +import de.robv.android.xposed.IXposedHookLoadPackage; +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XC_MethodReplacement; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.callbacks.XC_LoadPackage; +import tk.wasdennnoch.scoop.receiver.CrashReceiver; + +@SuppressWarnings("WeakerAccess") +public class XposedHook implements IXposedHookLoadPackage { + + private static String mPkg; + private Application mApplication; + private boolean mSent; + private final XC_MethodHook uncaughtExceptionHook = new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + if (mSent) { + Log.d("scoop", "uncaughtExceptionHook (" + mPkg + "): Broadcast already sent"); + return; + } + Log.d("scoop", "uncaughtExceptionHook (" + mPkg + "): Sending broadcast"); + Throwable t = (Throwable) param.args[1]; + Intent intent = new Intent(Intents.INTENT_ACTION) + .setClassName(BuildConfig.APPLICATION_ID, CrashReceiver.class.getName()) + .putExtra(Intents.INTENT_PACKAGE_NAME, mApplication.getPackageName()) + .putExtra(Intents.INTENT_TIME, System.currentTimeMillis()) + .putExtra(Intents.INTENT_DESCRIPTION, t.toString()) + .putExtra(Intents.INTENT_STACKTRACE, Log.getStackTraceString(t)); + // Just send everything here because it costs no performance (well, technically + // it does, but the process is about to die anyways, so I don't care). + // Also I have no idea how to detect custom subclasses efficiently. + mApplication.sendBroadcast(intent); + mSent = true; // Doesn't need to be reset as process dies soon + } + }; + private final XC_MethodHook setUncaughtExceptionHandlerHook = new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + if (param.args[0] != null) + hookUncaughtException(param.args[0].getClass()); + } + }; + + @Override + public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { + if (lpparam.packageName.equals("android")) return; + mPkg = lpparam.packageName; + XposedHelpers.findAndHookConstructor(Application.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { + mApplication = (Application) param.thisObject; + mSent = false; + } + }); + XposedHelpers.findAndHookMethod( + Thread.class, + "setDefaultUncaughtExceptionHandler", + Thread.UncaughtExceptionHandler.class, + setUncaughtExceptionHandlerHook); + + XposedHelpers.findAndHookMethod( + Thread.class, + "setUncaughtExceptionHandler", + Thread.UncaughtExceptionHandler.class, + setUncaughtExceptionHandlerHook); + + XposedHelpers.findAndHookMethod( + ThreadGroup.class, + "uncaughtException", + Thread.class, + Throwable.class, + uncaughtExceptionHook); + + // Gets initialized in between native application creation, handleLoadPackage gets + // called after native creation + hookUncaughtException(Thread.getDefaultUncaughtExceptionHandler().getClass()); + + if (lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) { + XposedHelpers.findAndHookMethod( + ScoopApplication.class, + "xposedActive", + XC_MethodReplacement.returnConstant(true)); + } + } + + private void hookUncaughtException(Class<?> clazz) { + int i = 0; + do { // Search through superclasses + try { + XposedHelpers.findAndHookMethod( + clazz, + "uncaughtException", + Thread.class, + Throwable.class, + uncaughtExceptionHook); + Log.d("scoop", "hookUncaughtException (" + mPkg + + "): Hooked class " + clazz.getName() + " after " + i + " loops"); + return; + } catch (Throwable ignore) { + } + i++; + } while ((clazz = clazz.getSuperclass()) != null); + Log.d("scoop", "hookUncaughtException (" + mPkg + "): No class found to hook!"); + } +} diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index f228baf..b51d8c3 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -4,6 +4,9 @@ Extremely useful for app debugging. Scoop supports both rooted and non-rooted devices (though non-rooted devices require some setup; see the repository wiki for details). +Scoop also supports Xposed, but only the GitHub releases will do so. F-Droid releases will NOT support Xposed. +If you want to use Scoop with Xposed, you must use the release builds from the GitHub repository. + Features: - Search (apps, stack traces) - Crash preview in notifications (configurable in settings)