diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9530550..7f6c145 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,6 +62,9 @@ android { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } + jniLibs { + useLegacyPackaging = true + } } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1832074..61589c6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,18 +1,23 @@ + xmlns:tools="http://schemas.android.com/tools" + android:installLocation="internalOnly"> + + mIgnoredPackages = new ArrayList<>(); + private Executor mExecutor = Executors.newCachedThreadPool(); private UsageStatsManager mUsageStatsManager; + private Snackbar mSnackBar = null; private Views mViews; static class Views { @@ -155,6 +161,60 @@ public void onDestroyView() { } } + private void killProcess(@NonNull String packageName) { + mSnackBar = Snackbar.make( + mViews.recyclerView, + R.string.snackbar_process_killing, + Snackbar.LENGTH_INDEFINITE); + mSnackBar.show(); + + mExecutor.execute(() -> { + sendAdbCommand("pm grant com.livefront.processkiller " + + "android.permission.WRITE_SECURE_SETTINGS " + + "&> /dev/null"); + + if (!sendAdbCommand("devices")) { + runOnUiThread(() -> { + mSnackBar.setText(R.string.snackbar_process_killing_failure_message) + .setDuration(Snackbar.LENGTH_LONG) + .show(); + }); + return; + } + if (!sendAdbCommand("shell am kill " + packageName)) { + runOnUiThread(() -> { + mSnackBar.setText(R.string.snackbar_process_killing_failure_message) + .setDuration(Snackbar.LENGTH_LONG) + .show(); + }); + return; + } + + runOnUiThread(() -> { + // We can defer the remaining logic to the legacy code + killProcessLegacy(packageName); + }); + }); + } + + private void killProcessLegacy(@NonNull String packageName) { + // This is no-op for Android 14+ + mActivityManager.killBackgroundProcesses(packageName); + + if (mSnackBar == null) { + mSnackBar = Snackbar.make( + mViews.recyclerView, + R.string.snackbar_process_killed_action, + Snackbar.LENGTH_LONG); + } + mSnackBar.setText(R.string.snackbar_process_killed_action) + .setDuration(Snackbar.LENGTH_LONG) + .setAction( + R.string.snackbar_process_killed_action, + v -> launchIntentForPackage(packageName)) + .show(); + } + /** * Attempts to find (and launch) an intent to resume an application from its last known running * task. In most cases this should simulate clicking on the application on the Recents screen. @@ -245,6 +305,14 @@ public void onTaskComplete(@NonNull List processDetails) { mProcessDetailTask.execute(); } + private void runOnUiThread(@NonNull Runnable runnable) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + activity.runOnUiThread(runnable); + } + private void setupRecyclerView() { LinearLayoutManager linearLayoutManager = new LinearLayoutManager( getActivity(), @@ -256,24 +324,31 @@ private void setupRecyclerView() { mAdapter.setOnProcessDetailClickListener(new OnProcessDetailClickListener() { @Override public void onProcessDetailClick(@NonNull final ProcessDetail processDetail) { - mActivityManager.killBackgroundProcesses(processDetail.getPackageName()); - Snackbar.make( - mViews.recyclerView, - R.string.snackbar_process_killed_message, - Snackbar.LENGTH_LONG) - .setAction( - R.string.snackbar_process_killed_action, - new View.OnClickListener() { - @Override - public void onClick(View v) { - launchIntentForPackage(processDetail.getPackageName()); - } - }) - .show(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + killProcessLegacy(processDetail.getPackageName()); + } else { + killProcess(processDetail.getPackageName()); + } } }); } + private boolean sendAdbCommand(String command) { + Activity activity = getActivity(); + if (activity == null) { + return false; + } + // Note that this makes use of ADB libraries from + // https://github.com/tytydraco/LADB/tree/main/app/src/main/jniLibs + String adbPath = activity.getApplicationInfo().nativeLibraryDir + "/libadb.so"; + String[] cmdLine = {"sh", "-c", adbPath + " " + command}; + try { + return Runtime.getRuntime().exec(cmdLine).waitFor() == 0; + } catch (IOException | InterruptedException e) { + return false; + } + } + private void showProgress(boolean show) { mViews.spinner.setVisibility(show ? View.VISIBLE : View.GONE); } diff --git a/app/src/main/jniLibs/arm64-v8a/libadb.so b/app/src/main/jniLibs/arm64-v8a/libadb.so new file mode 100644 index 0000000..572b479 Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libadb.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libadb.so b/app/src/main/jniLibs/armeabi-v7a/libadb.so new file mode 100644 index 0000000..69826a3 Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libadb.so differ diff --git a/app/src/main/jniLibs/x86/libadb.so b/app/src/main/jniLibs/x86/libadb.so new file mode 100644 index 0000000..90b46d8 Binary files /dev/null and b/app/src/main/jniLibs/x86/libadb.so differ diff --git a/app/src/main/jniLibs/x86_64/libadb.so b/app/src/main/jniLibs/x86_64/libadb.so new file mode 100644 index 0000000..86cd051 Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libadb.so differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e3f73ca..9462e0f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,8 @@ Process killed if running Go to app + Killing + Failed to kill That application can not be launched