commit 87ed2a2ce2b12fc49d5725142fa0a780587d4926 Author: ajp_anton Date: Wed Apr 22 22:32:15 2026 +0000 Initial import from killapp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a7e04a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.gradle/ +build/ +app/build/ +local.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..c26c768 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,96 @@ +import java.io.File +import java.util.Properties + +plugins { + id("com.android.application") +} + +val releaseSigningProperties = Properties() +val releaseSigningPropertiesFile = File( + System.getProperty("user.home"), + ".config/android/release-signing.properties" +) +if (releaseSigningPropertiesFile.isFile) { + releaseSigningPropertiesFile.inputStream().use(releaseSigningProperties::load) +} + +val releaseKeystorePath = releaseSigningProperties.getProperty("storeFile") +val releaseKeyAlias = releaseSigningProperties.getProperty("keyAlias") +val releaseStorePassword = providers.gradleProperty("androidReleaseStorePassword").orNull +val releaseKeyPassword = providers.gradleProperty("androidReleaseKeyPassword").orNull +val hasSharedReleaseSigningConfig = + !releaseKeystorePath.isNullOrBlank() && !releaseKeyAlias.isNullOrBlank() +val hasSharedReleaseSigningSecrets = + !releaseStorePassword.isNullOrBlank() && !releaseKeyPassword.isNullOrBlank() +val requestedTaskNames = gradle.startParameter.taskNames.map(String::lowercase) +val releaseBuildRequested = requestedTaskNames.any { "release" in it } + +if (releaseBuildRequested && (!hasSharedReleaseSigningConfig || !hasSharedReleaseSigningSecrets)) { + error( + "Release signing is not configured. " + + "Create ~/.config/android/release-signing.properties and provide " + + "androidReleaseStorePassword/androidReleaseKeyPassword." + ) +} + +android { + namespace = "se.ajpanton.killappnow" + compileSdk = 36 + dependenciesInfo { + includeInApk = false + includeInBundle = false + } + + signingConfigs { + if (hasSharedReleaseSigningConfig && hasSharedReleaseSigningSecrets) { + create("release") { + storeFile = file(releaseKeystorePath!!) + storePassword = releaseStorePassword + keyAlias = releaseKeyAlias + keyPassword = releaseKeyPassword + } + } + } + + defaultConfig { + applicationId = "se.ajpanton.killappnow" + minSdk = 35 + targetSdk = 35 + versionCode = 1702 + versionName = "17.2" + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + if (hasSharedReleaseSigningConfig && hasSharedReleaseSigningSecrets) { + signingConfig = signingConfigs.getByName("release") + } + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + 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 { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +dependencies { + + implementation("androidx.annotation:annotation:1.7.1") + implementation("org.lsposed.hiddenapibypass:hiddenapibypass:6.1") + compileOnly("io.github.libxposed:api:101.0.0") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..9418c21 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,28 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-dontobfuscate + +-keep class se.ajpanton.killappnow.KillAppNow +-keepclassmembers class se.ajpanton.killappnow.KillAppNow { + public (); +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e6e334b --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/java/se/ajpanton/killappnow/KillAppNow.java b/app/src/main/java/se/ajpanton/killappnow/KillAppNow.java new file mode 100644 index 0000000..2f0abf1 --- /dev/null +++ b/app/src/main/java/se/ajpanton/killappnow/KillAppNow.java @@ -0,0 +1,421 @@ +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); + } + } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..028cf62 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + KillAppNow + Kill foreground app on "Back" button long-press. + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/resources/META-INF/xposed/java_init.list b/app/src/main/resources/META-INF/xposed/java_init.list new file mode 100644 index 0000000..1a0cb86 --- /dev/null +++ b/app/src/main/resources/META-INF/xposed/java_init.list @@ -0,0 +1 @@ +se.ajpanton.killappnow.KillAppNow diff --git a/app/src/main/resources/META-INF/xposed/module.prop b/app/src/main/resources/META-INF/xposed/module.prop new file mode 100644 index 0000000..c3975fe --- /dev/null +++ b/app/src/main/resources/META-INF/xposed/module.prop @@ -0,0 +1,3 @@ +minApiVersion=101 +targetApiVersion=101 +staticScope=true diff --git a/app/src/main/resources/META-INF/xposed/scope.list b/app/src/main/resources/META-INF/xposed/scope.list new file mode 100644 index 0000000..90efcf1 --- /dev/null +++ b/app/src/main/resources/META-INF/xposed/scope.list @@ -0,0 +1,2 @@ +com.android.systemui +com.sec.android.app.launcher diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..5a3a86f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,4 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.android.application") version "9.1.1" apply false +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..a03b354 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9aacd8a --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jan 13 07:04:02 EET 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..529f072 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "KillAppNow" +include(":app")