Rename project to NavButtons and convert module to Kotlin

This commit is contained in:
ajp_anton
2026-04-22 23:13:24 +00:00
parent 87ed2a2ce2
commit cade5e360e
7 changed files with 449 additions and 434 deletions
+4 -7
View File
@@ -34,7 +34,7 @@ if (releaseBuildRequested && (!hasSharedReleaseSigningConfig || !hasSharedReleas
}
android {
namespace = "se.ajpanton.killappnow"
namespace = "se.ajpanton.navbuttons"
compileSdk = 36
dependenciesInfo {
includeInApk = false
@@ -53,11 +53,11 @@ android {
}
defaultConfig {
applicationId = "se.ajpanton.killappnow"
applicationId = "se.ajpanton.navbuttons"
minSdk = 35
targetSdk = 35
versionCode = 1702
versionName = "17.2"
versionCode = 1
versionName = "0.1"
}
buildTypes {
@@ -73,9 +73,6 @@ android {
packaging {
resources {
merges += "META-INF/xposed/*"
// Not needed as long as we don't use reflection with Kotlin
excludes.add("**/*.kotlin_builtins")
excludes.add("**/*.kotlin_module")
}
}
compileOptions {
+2 -2
View File
@@ -22,7 +22,7 @@
-dontobfuscate
-keep class se.ajpanton.killappnow.KillAppNow
-keepclassmembers class se.ajpanton.killappnow.KillAppNow {
-keep class se.ajpanton.navbuttons.NavButtons
-keepclassmembers class se.ajpanton.navbuttons.NavButtons {
public <init>();
}
@@ -1,421 +0,0 @@
package se.ajpanton.killappnow;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import io.github.libxposed.api.XposedModule;
import org.lsposed.hiddenapibypass.HiddenApiBypass;
public class KillAppNow extends XposedModule {
private static final String TAG = "KillAppNow";
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static final String SAMSUNG_LAUNCHER_PACKAGE = "com.sec.android.app.launcher";
private static final String HONEYSPACE_NAV_BUTTON_VIEW =
"com.honeyspace.ui.honeypots.taskbar.presentation.NavigationBarKeyButtonView";
private static final String HONEYSPACE_LONG_PRESS_RUNNABLE = "f.a";
private static final int HONEYSPACE_LONG_PRESS_CASE = 8;
private Context mContext;
private Handler mMainHandler;
private boolean mSystemUiHookInstalled;
private boolean mLauncherHookInstalled;
public KillAppNow() {
// LSPosed loads modern module entries reflectively via a public no-arg constructor.
}
@Override
public void onModuleLoaded(@NonNull ModuleLoadedParam param) {
if (!HiddenApiBypass.addHiddenApiExemptions("")) {
log("HiddenApiBypass could not be enabled.");
}
}
@Override
public void onPackageLoaded(@NonNull PackageLoadedParam param) {
handleTargetPackage(param.getPackageName(), param.getDefaultClassLoader());
}
@Override
public void onPackageReady(@NonNull PackageReadyParam param) {
handleTargetPackage(param.getPackageName(), param.getClassLoader());
}
private void handleTargetPackage(String packageName, ClassLoader classLoader) {
if (SYSTEMUI_PACKAGE.equals(packageName)) {
if (mSystemUiHookInstalled) {
return;
}
initContextAndHandler();
mSystemUiHookInstalled = hookSystemUiBackLongPress(classLoader);
return;
}
if (SAMSUNG_LAUNCHER_PACKAGE.equals(packageName)) {
if (mLauncherHookInstalled) {
return;
}
initContextAndHandler();
mLauncherHookInstalled = hookHoneyspaceTaskbar(classLoader);
}
}
private void initContextAndHandler() {
if (mContext == null) {
mContext = getCurrentApplication();
}
if (mMainHandler == null) {
mMainHandler = new Handler(Looper.getMainLooper());
}
}
private Context getCurrentApplication() {
try {
Object application = callStaticMethod(
findClass("android.app.ActivityThread", null),
"currentApplication");
return application instanceof Context ? (Context) application : null;
} catch (Throwable t) {
log("currentApplication lookup failed: " + t.getClass().getSimpleName());
return null;
}
}
private boolean hookSystemUiBackLongPress(ClassLoader classLoader) {
Class<?> runnableClass = findClassIfExists(
"com.android.systemui.navigationbar.views.buttons.KeyButtonView$1", classLoader);
if (runnableClass == null) {
log("SystemUI long-press runnable not found.");
return false;
}
Method runMethod = findMethodIfExists(runnableClass, "run");
if (runMethod == null) {
log("SystemUI run() method not found.");
return false;
}
deoptimizeMethodIfPresent(findMethodIfExists(Handler.class, "dispatchMessage", android.os.Message.class));
deoptimizeMethodIfPresent(findMethodIfExists(Handler.class, "handleCallback", android.os.Message.class));
deoptimizeMethodIfPresent(runMethod);
hook(runMethod).intercept(chain -> {
Object keyButtonView = getFieldValue(chain.getThisObject(), "this$0");
int code = getIntField(keyButtonView, "mCode");
if (code != KeyEvent.KEYCODE_BACK) {
return chain.proceed();
}
updateContextFromView(keyButtonView);
markSystemUiLongClicked(keyButtonView);
killForegroundApp();
return null;
});
return true;
}
private boolean hookHoneyspaceTaskbar(ClassLoader classLoader) {
Class<?> navButtonClass = findClassIfExists(HONEYSPACE_NAV_BUTTON_VIEW, classLoader);
if (navButtonClass == null) {
log("Launcher class not found: " + HONEYSPACE_NAV_BUTTON_VIEW);
return false;
}
Class<?> runnableClass = findClassIfExists(HONEYSPACE_LONG_PRESS_RUNNABLE, classLoader);
if (runnableClass == null) {
log("Launcher long-press runnable not found: " + HONEYSPACE_LONG_PRESS_RUNNABLE);
return false;
}
Method runMethod = findMethodIfExists(runnableClass, "run");
if (runMethod == null) {
log("Launcher run() method not found.");
return false;
}
deoptimizeMethodIfPresent(findMethodIfExists(Handler.class, "dispatchMessage", android.os.Message.class));
deoptimizeMethodIfPresent(findMethodIfExists(Handler.class, "handleCallback", android.os.Message.class));
deoptimizeMethodIfPresent(runMethod);
hook(runMethod).intercept(chain -> {
int caseId = getIntField(chain.getThisObject(), "b");
if (caseId != HONEYSPACE_LONG_PRESS_CASE) {
return chain.proceed();
}
Object target = getFieldValue(chain.getThisObject(), "c");
if (!navButtonClass.isInstance(target)) {
return chain.proceed();
}
int keyCode = ((Integer) callMethod(target, "getKeyCode")).intValue();
if (keyCode != KeyEvent.KEYCODE_BACK) {
return chain.proceed();
}
updateContextFromView(target);
markHoneyspaceLongClicked(target);
killForegroundApp();
return null;
});
return true;
}
private void markSystemUiLongClicked(Object keyButtonView) {
setBooleanField(keyButtonView, "mLongClicked", true);
}
private void markHoneyspaceLongClicked(Object navButtonView) {
try {
callMethod(navButtonView, "setLongClicked", new Class<?>[]{boolean.class}, true);
} catch (Throwable t) {
log("setLongClicked failed: " + t.getClass().getSimpleName());
}
}
private void updateContextFromView(Object view) {
View v = (View) view;
Context ctx = v.getContext();
Context appCtx = ctx.getApplicationContext();
mContext = appCtx != null ? appCtx : ctx;
}
private void killForegroundApp() {
if (mMainHandler == null) {
initContextAndHandler();
}
if (mMainHandler == null) {
log("Main handler unavailable.");
return;
}
mMainHandler.post(() -> {
try {
if (mContext == null) {
log("Context unavailable, cannot kill app.");
return;
}
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
String defaultLauncher = getDefaultLauncherPackageName();
String foregroundApp = getTopPackageName();
if (foregroundApp != null && !shouldSkipPackage(foregroundApp, defaultLauncher)) {
String appLabel = getApplicationLabel(foregroundApp, mContext.getPackageManager());
log("Killing: " + foregroundApp + " (" + appLabel + ")");
callMethod(am, "forceStopPackage", new Class<?>[]{String.class}, foregroundApp);
showToast("Killed: " + appLabel);
} else {
log("Nothing to kill.");
showToast("Nothing to kill.");
}
} catch (Throwable t) {
Log.e(TAG, "Error in killForegroundApp", t);
log("Error in killForegroundApp", t);
}
});
}
private String getDefaultLauncherPackageName() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
ResolveInfo resolveInfo = mContext.getPackageManager()
.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfo != null) {
return resolveInfo.activityInfo.packageName;
}
return null;
}
private String getTopPackageName() {
return getTopPackageFromActivityTaskManager();
}
private String getTopPackageFromActivityTaskManager() {
try {
Object service = callStaticMethod(
findClass("android.app.ActivityTaskManager", null),
"getService");
Object taskInfo = callMethod(service, "getFocusedRootTaskInfo");
return extractPackageFromTaskInfo(taskInfo);
} catch (Throwable t) {
log("ActivityTaskManager.getFocusedRootTaskInfo failed: " + t.getClass().getSimpleName());
return null;
}
}
private String extractPackageFromTaskInfo(Object taskInfo) {
if (taskInfo == null) {
return null;
}
String pkg = getPackageNameFromComponent(getFieldValue(taskInfo, "topActivity"));
if (pkg != null) {
return pkg;
}
return getPackageNameFromActivityInfo(getFieldValue(taskInfo, "topActivityInfo"));
}
private Object getFieldValue(Object target, String fieldName) {
if (target == null) {
return null;
}
try {
return findField(target.getClass(), fieldName).get(target);
} catch (Throwable t) {
return null;
}
}
private int getIntField(Object target, String fieldName) {
Object value = getFieldValue(target, fieldName);
return value instanceof Integer ? (Integer) value : Integer.MIN_VALUE;
}
private String getPackageNameFromComponent(Object component) {
if (component instanceof ComponentName) {
return ((ComponentName) component).getPackageName();
}
return null;
}
private String getPackageNameFromActivityInfo(Object info) {
if (info == null) {
return null;
}
try {
Object pkg = findField(info.getClass(), "packageName").get(info);
return pkg != null ? pkg.toString() : null;
} catch (Throwable t) {
return null;
}
}
private boolean shouldSkipPackage(String packageName, String defaultLauncher) {
if (packageName == null) {
return true;
}
if (SYSTEMUI_PACKAGE.equals(packageName)) {
return true;
}
if (SAMSUNG_LAUNCHER_PACKAGE.equals(packageName)) {
return true;
}
return defaultLauncher != null && defaultLauncher.equals(packageName);
}
private String getApplicationLabel(String packageName, PackageManager pm) {
try {
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
return (String) pm.getApplicationLabel(appInfo);
} catch (PackageManager.NameNotFoundException e) {
return packageName;
}
}
private void showToast(String message) {
try {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
} catch (Throwable t) {
log("Toast failed: " + t.getClass().getSimpleName());
}
}
private void log(String message) {
log(Log.INFO, TAG, message);
}
private void log(String message, Throwable t) {
log(Log.ERROR, TAG, message, t);
}
private void deoptimizeMethodIfPresent(Method method) {
if (method != null) {
deoptimize(method);
}
}
private Class<?> findClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
return Class.forName(className, false, classLoader);
}
private Class<?> findClassIfExists(String className, ClassLoader classLoader) {
try {
return findClass(className, classLoader);
} catch (Throwable t) {
return null;
}
}
private Method findMethodIfExists(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
try {
return findMethod(clazz, methodName, parameterTypes);
} catch (Throwable t) {
return null;
}
}
private Method findMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> current = clazz; current != null; current = current.getSuperclass()) {
try {
Method method = current.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException ignored) {
// Continue searching the class hierarchy.
}
}
throw new NoSuchMethodException(clazz.getName() + "#" + methodName);
}
private Field findField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
for (Class<?> current = clazz; current != null; current = current.getSuperclass()) {
try {
Field field = current.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException ignored) {
// Continue searching the class hierarchy.
}
}
throw new NoSuchFieldException(clazz.getName() + "#" + fieldName);
}
private Object callStaticMethod(Class<?> clazz, String methodName)
throws ReflectiveOperationException {
return findMethod(clazz, methodName).invoke(null);
}
private Object callMethod(Object target, String methodName)
throws ReflectiveOperationException {
return callMethod(target, methodName, new Class<?>[0]);
}
private Object callMethod(Object target, String methodName, Class<?>[] parameterTypes, Object... args)
throws ReflectiveOperationException {
return findMethod(target.getClass(), methodName, parameterTypes).invoke(target, args);
}
private void setBooleanField(Object target, String fieldName, boolean value) {
try {
findField(target.getClass(), fieldName).setBoolean(target, value);
} catch (Throwable t) {
log("Failed to set " + fieldName, t);
}
}
}
@@ -0,0 +1,439 @@
package se.ajpanton.navbuttons
import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.widget.Toast
import io.github.libxposed.api.XposedModule
import io.github.libxposed.api.XposedModuleInterface
import org.lsposed.hiddenapibypass.HiddenApiBypass
import java.lang.reflect.Field
import java.lang.reflect.Method
class NavButtons : XposedModule() {
private var context: Context? = null
private var mainHandler: Handler? = null
private var systemUiHookInstalled = false
private var launcherHookInstalled = false
override fun onModuleLoaded(param: XposedModuleInterface.ModuleLoadedParam) {
if (!HiddenApiBypass.addHiddenApiExemptions("")) {
log("HiddenApiBypass could not be enabled.")
}
}
override fun onPackageLoaded(param: XposedModuleInterface.PackageLoadedParam) {
handleTargetPackage(param.packageName, param.defaultClassLoader)
}
override fun onPackageReady(param: XposedModuleInterface.PackageReadyParam) {
handleTargetPackage(param.packageName, param.classLoader)
}
private fun handleTargetPackage(packageName: String, classLoader: ClassLoader?) {
if (SYSTEMUI_PACKAGE == packageName) {
if (systemUiHookInstalled) {
return
}
initContextAndHandler()
systemUiHookInstalled = hookSystemUiBackLongPress(classLoader)
return
}
if (SAMSUNG_LAUNCHER_PACKAGE == packageName) {
if (launcherHookInstalled) {
return
}
initContextAndHandler()
launcherHookInstalled = hookHoneyspaceTaskbar(classLoader)
}
}
private fun initContextAndHandler() {
if (context == null) {
context = getCurrentApplication()
}
if (mainHandler == null) {
mainHandler = Handler(Looper.getMainLooper())
}
}
private fun getCurrentApplication(): Context? {
return try {
val application = callStaticMethod(findClass("android.app.ActivityThread", null), "currentApplication")
application as? Context
} catch (t: Throwable) {
log("currentApplication lookup failed: ${t.javaClass.simpleName}")
null
}
}
private fun hookSystemUiBackLongPress(classLoader: ClassLoader?): Boolean {
val runnableClass = findClassIfExists(
"com.android.systemui.navigationbar.views.buttons.KeyButtonView\$1",
classLoader,
) ?: run {
log("SystemUI long-press runnable not found.")
return false
}
val runMethod = findMethodIfExists(runnableClass, "run") ?: run {
log("SystemUI run() method not found.")
return false
}
prepareLongPressHook(runMethod)
hook(runMethod).intercept { chain ->
val keyButtonView = getFieldValue(chain.thisObject, "this\$0")
?: return@intercept chain.proceed()
val code = getIntField(keyButtonView, "mCode")
if (code != KeyEvent.KEYCODE_BACK) {
return@intercept chain.proceed()
}
updateContextFromView(keyButtonView)
markSystemUiLongClicked(keyButtonView)
killForegroundApp()
null
}
return true
}
private fun hookHoneyspaceTaskbar(classLoader: ClassLoader?): Boolean {
val navButtonClass = findClassIfExists(HONEYSPACE_NAV_BUTTON_VIEW, classLoader) ?: run {
log("Launcher class not found: $HONEYSPACE_NAV_BUTTON_VIEW")
return false
}
val runnableClass = findClassIfExists(HONEYSPACE_LONG_PRESS_RUNNABLE, classLoader) ?: run {
log("Launcher long-press runnable not found: $HONEYSPACE_LONG_PRESS_RUNNABLE")
return false
}
val runMethod = findMethodIfExists(runnableClass, "run") ?: run {
log("Launcher run() method not found.")
return false
}
prepareLongPressHook(runMethod)
hook(runMethod).intercept { chain ->
val caseId = getIntField(chain.thisObject, "b")
if (caseId != HONEYSPACE_LONG_PRESS_CASE) {
return@intercept chain.proceed()
}
val target = getFieldValue(chain.thisObject, "c")
if (!navButtonClass.isInstance(target)) {
return@intercept chain.proceed()
}
val navButtonView = target ?: return@intercept chain.proceed()
val keyCode = callMethod(navButtonView, "getKeyCode") as Int
if (keyCode != KeyEvent.KEYCODE_BACK) {
return@intercept chain.proceed()
}
updateContextFromView(navButtonView)
markHoneyspaceLongClicked(navButtonView)
killForegroundApp()
null
}
return true
}
private fun markSystemUiLongClicked(keyButtonView: Any) {
setBooleanField(keyButtonView, "mLongClicked", true)
}
private fun markHoneyspaceLongClicked(navButtonView: Any) {
try {
callMethod(
navButtonView,
"setLongClicked",
arrayOf(Boolean::class.javaPrimitiveType!!),
true,
)
} catch (t: Throwable) {
log("setLongClicked failed: ${t.javaClass.simpleName}")
}
}
private fun updateContextFromView(view: Any) {
val currentContext = (view as View).context
context = currentContext.applicationContext ?: currentContext
}
private fun killForegroundApp() {
if (mainHandler == null) {
initContextAndHandler()
}
val handler = mainHandler
if (handler == null) {
log("Main handler unavailable.")
return
}
handler.post {
try {
val currentContext = context
if (currentContext == null) {
log("Context unavailable, cannot kill app.")
return@post
}
val activityManager =
currentContext.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
if (activityManager == null) {
log("ActivityManager unavailable.")
return@post
}
val defaultLauncher = getDefaultLauncherPackageName()
val foregroundApp = getTopPackageName()
if (foregroundApp != null && !shouldSkipPackage(foregroundApp, defaultLauncher)) {
val appLabel = getApplicationLabel(foregroundApp, currentContext.packageManager)
log("Killing: $foregroundApp ($appLabel)")
callMethod(
activityManager,
"forceStopPackage",
arrayOf(String::class.java),
foregroundApp,
)
showToast("Killed: $appLabel")
} else {
log("Nothing to kill.")
showToast("Nothing to kill.")
}
} catch (t: Throwable) {
Log.e(TAG, "Error in killForegroundApp", t)
log("Error in killForegroundApp", t)
}
}
}
private fun getDefaultLauncherPackageName(): String? {
val currentContext = context ?: return null
val intent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_HOME)
}
val resolveInfo = currentContext.packageManager.resolveActivity(
intent,
PackageManager.MATCH_DEFAULT_ONLY,
)
return resolveInfo?.activityInfo?.packageName
}
private fun getTopPackageName(): String? = getTopPackageFromActivityTaskManager()
private fun getTopPackageFromActivityTaskManager(): String? {
return try {
val service = callStaticMethod(
findClass("android.app.ActivityTaskManager", null),
"getService",
) ?: return null
val taskInfo = callMethod(service, "getFocusedRootTaskInfo")
extractPackageFromTaskInfo(taskInfo)
} catch (t: Throwable) {
log("ActivityTaskManager.getFocusedRootTaskInfo failed: ${t.javaClass.simpleName}")
null
}
}
private fun extractPackageFromTaskInfo(taskInfo: Any?): String? {
if (taskInfo == null) {
return null
}
val pkg = getPackageNameFromComponent(getFieldValue(taskInfo, "topActivity"))
return pkg ?: getPackageNameFromActivityInfo(getFieldValue(taskInfo, "topActivityInfo"))
}
private fun getFieldValue(target: Any?, fieldName: String): Any? {
if (target == null) {
return null
}
return try {
findField(target.javaClass, fieldName).get(target)
} catch (_: Throwable) {
null
}
}
private fun getIntField(target: Any?, fieldName: String): Int {
val value = getFieldValue(target, fieldName)
return value as? Int ?: Int.MIN_VALUE
}
private fun getPackageNameFromComponent(component: Any?): String? {
return (component as? ComponentName)?.packageName
}
private fun getPackageNameFromActivityInfo(info: Any?): String? {
if (info == null) {
return null
}
return try {
findField(info.javaClass, "packageName").get(info)?.toString()
} catch (_: Throwable) {
null
}
}
private fun shouldSkipPackage(packageName: String?, defaultLauncher: String?): Boolean {
return packageName == null ||
packageName == SYSTEMUI_PACKAGE ||
packageName == SAMSUNG_LAUNCHER_PACKAGE ||
packageName == defaultLauncher
}
private fun getApplicationLabel(packageName: String, packageManager: PackageManager): String {
return try {
val appInfo: ApplicationInfo = packageManager.getApplicationInfo(packageName, 0)
packageManager.getApplicationLabel(appInfo).toString()
} catch (_: PackageManager.NameNotFoundException) {
packageName
}
}
private fun showToast(message: String) {
val currentContext = context ?: return
try {
Toast.makeText(currentContext, message, Toast.LENGTH_SHORT).show()
} catch (t: Throwable) {
log("Toast failed: ${t.javaClass.simpleName}")
}
}
private fun log(message: String) {
log(Log.INFO, TAG, message)
}
private fun log(message: String, throwable: Throwable) {
log(Log.ERROR, TAG, message, throwable)
}
private fun deoptimizeMethodIfPresent(method: Method?) {
if (method != null) {
deoptimize(method)
}
}
private fun prepareLongPressHook(runMethod: Method) {
deoptimizeMethodIfPresent(
findMethodIfExists(Handler::class.java, "dispatchMessage", android.os.Message::class.java),
)
deoptimizeMethodIfPresent(
findMethodIfExists(Handler::class.java, "handleCallback", android.os.Message::class.java),
)
deoptimizeMethodIfPresent(runMethod)
}
private fun findClass(className: String, classLoader: ClassLoader?): Class<*> {
return Class.forName(className, false, classLoader)
}
private fun findClassIfExists(className: String, classLoader: ClassLoader?): Class<*>? {
return try {
findClass(className, classLoader)
} catch (_: Throwable) {
null
}
}
private fun findMethodIfExists(
clazz: Class<*>,
methodName: String,
vararg parameterTypes: Class<*>,
): Method? {
return try {
findMethod(clazz, methodName, *parameterTypes)
} catch (_: Throwable) {
null
}
}
@Throws(NoSuchMethodException::class)
private fun findMethod(
clazz: Class<*>,
methodName: String,
vararg parameterTypes: Class<*>,
): Method {
var current: Class<*>? = clazz
while (current != null) {
try {
return current.getDeclaredMethod(methodName, *parameterTypes).apply {
isAccessible = true
}
} catch (_: NoSuchMethodException) {
current = current.superclass
}
}
throw NoSuchMethodException("${clazz.name}#$methodName")
}
@Throws(NoSuchFieldException::class)
private fun findField(clazz: Class<*>, fieldName: String): Field {
var current: Class<*>? = clazz
while (current != null) {
try {
return current.getDeclaredField(fieldName).apply {
isAccessible = true
}
} catch (_: NoSuchFieldException) {
current = current.superclass
}
}
throw NoSuchFieldException("${clazz.name}#$fieldName")
}
@Throws(ReflectiveOperationException::class)
private fun callStaticMethod(clazz: Class<*>, methodName: String): Any? {
return findMethod(clazz, methodName).invoke(null)
}
@Throws(ReflectiveOperationException::class)
private fun callMethod(target: Any, methodName: String): Any? {
return callMethod(target, methodName, emptyArray())
}
@Throws(ReflectiveOperationException::class)
private fun callMethod(
target: Any,
methodName: String,
parameterTypes: Array<Class<*>>,
vararg args: Any?,
): Any? {
return findMethod(target.javaClass, methodName, *parameterTypes).invoke(target, *args)
}
private fun setBooleanField(target: Any, fieldName: String, value: Boolean) {
try {
findField(target.javaClass, fieldName).setBoolean(target, value)
} catch (t: Throwable) {
log("Failed to set $fieldName", t)
}
}
private companion object {
const val TAG = "NavButtons"
const val SYSTEMUI_PACKAGE = "com.android.systemui"
const val SAMSUNG_LAUNCHER_PACKAGE = "com.sec.android.app.launcher"
const val HONEYSPACE_NAV_BUTTON_VIEW =
"com.honeyspace.ui.honeypots.taskbar.presentation.NavigationBarKeyButtonView"
const val HONEYSPACE_LONG_PRESS_RUNNABLE = "f.a"
const val HONEYSPACE_LONG_PRESS_CASE = 8
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
<resources>
<string name="app_name">KillAppNow</string>
<string name="app_name">NavButtons</string>
<string name="xposed_desc">Kill foreground app on "Back" button long-press.</string>
</resources>
@@ -1 +1 @@
se.ajpanton.killappnow.KillAppNow
se.ajpanton.navbuttons.NavButtons
+1 -1
View File
@@ -13,5 +13,5 @@ dependencyResolutionManagement {
}
}
rootProject.name = "KillAppNow"
rootProject.name = "NavButtons"
include(":app")