Skip to content

Commit 27f781e

Browse files
WIP: Use ADB to kill processes
1 parent 97fef49 commit 27f781e

8 files changed

Lines changed: 101 additions & 15 deletions

File tree

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ android {
6262
resources {
6363
excludes += "/META-INF/{AL2.0,LGPL2.1}"
6464
}
65+
jniLibs {
66+
useLegacyPackaging = true
67+
}
6568
}
6669
}
6770

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools">
3+
xmlns:tools="http://schemas.android.com/tools"
4+
android:installLocation="internalOnly">
45

6+
<uses-permission android:name="android.permission.INTERNET" />
57
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
68
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
79
tools:ignore="ProtectedPermissions"/>
810
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
911
tools:ignore="QueryAllPackagesPermission" />
12+
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
13+
tools:ignore="ProtectedPermissions" />
1014

1115
<application
1216
android:allowBackup="true"
1317
android:icon="@mipmap/ic_launcher"
1418
android:label="@string/app_name"
1519
android:supportsRtl="true"
20+
android:extractNativeLibs="true"
1621
android:theme="@style/AppTheme" >
1722
<activity
1823
android:name=".activity.ProcessActivity"

app/src/main/java/com/livefront/processkiller/fragment/ProcessDetailFragment.java

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.app.usage.UsageStatsManager;
66
import android.content.Intent;
77
import android.content.pm.PackageManager;
8+
import android.os.Build;
89
import android.os.Bundle;
910
import android.view.LayoutInflater;
1011
import android.view.View;
@@ -24,6 +25,7 @@
2425
import com.livefront.processkiller.model.ProcessDetail;
2526
import com.livefront.processkiller.task.ProcessDetailTask;
2627

28+
import java.io.IOException;
2729
import java.lang.annotation.Retention;
2830
import java.lang.annotation.RetentionPolicy;
2931
import java.util.ArrayList;
@@ -32,6 +34,8 @@
3234
import java.util.Collections;
3335
import java.util.Comparator;
3436
import java.util.List;
37+
import java.util.concurrent.Executor;
38+
import java.util.concurrent.Executors;
3539
import java.util.concurrent.TimeUnit;
3640

3741
/**
@@ -87,12 +91,15 @@ public int compare(ProcessDetail lhs, ProcessDetail rhs) {
8791
private ProcessDetailTask mProcessDetailTask;
8892
private PackageManager mPackageManager;
8993
private List<String> mIgnoredPackages = new ArrayList<>();
94+
private Executor mExecutor = Executors.newCachedThreadPool();
9095
private UsageStatsManager mUsageStatsManager;
96+
private Snackbar mSnackBar = null;
9197
private Views mViews;
9298

9399
static class Views {
94100
RecyclerView recyclerView;
95101
View spinner;
102+
96103
Views(View root) {
97104
recyclerView = (RecyclerView) root.findViewById(R.id.recycler_view);
98105
spinner = root.findViewById(R.id.spinner);
@@ -155,6 +162,60 @@ public void onDestroyView() {
155162
}
156163
}
157164

165+
private void killProcess(@NonNull String packageName) {
166+
mSnackBar = Snackbar.make(
167+
mViews.recyclerView,
168+
R.string.snackbar_process_killing,
169+
Snackbar.LENGTH_INDEFINITE);
170+
mSnackBar.show();
171+
172+
mExecutor.execute(() -> {
173+
sendAdbCommand("pm grant com.livefront.processkiller "
174+
+ "android.permission.WRITE_SECURE_SETTINGS "
175+
+ "&> /dev/null");
176+
177+
if (!sendAdbCommand("devices")) {
178+
runOnUiThread(() -> {
179+
mSnackBar.setText(R.string.snackbar_process_killing_failure_message)
180+
.setDuration(Snackbar.LENGTH_LONG)
181+
.show();
182+
});
183+
return;
184+
}
185+
if (!sendAdbCommand("shell am kill " + packageName)) {
186+
runOnUiThread(() -> {
187+
mSnackBar.setText(R.string.snackbar_process_killing_failure_message)
188+
.setDuration(Snackbar.LENGTH_LONG)
189+
.show();
190+
});
191+
return;
192+
}
193+
194+
runOnUiThread(() -> {
195+
// We can defer the remaining logic to the legacy code
196+
killProcessLegacy(packageName);
197+
});
198+
});
199+
}
200+
201+
private void killProcessLegacy(@NonNull String packageName) {
202+
// This is no-op for Android 14+
203+
mActivityManager.killBackgroundProcesses(packageName);
204+
205+
if (mSnackBar == null) {
206+
mSnackBar = Snackbar.make(
207+
mViews.recyclerView,
208+
R.string.snackbar_process_killed_action,
209+
Snackbar.LENGTH_LONG);
210+
}
211+
mSnackBar.setText(R.string.snackbar_process_killed_action)
212+
.setDuration(Snackbar.LENGTH_LONG)
213+
.setAction(
214+
R.string.snackbar_process_killed_action,
215+
v -> launchIntentForPackage(packageName))
216+
.show();
217+
}
218+
158219
/**
159220
* Attempts to find (and launch) an intent to resume an application from its last known running
160221
* task. In most cases this should simulate clicking on the application on the Recents screen.
@@ -245,6 +306,14 @@ public void onTaskComplete(@NonNull List<ProcessDetail> processDetails) {
245306
mProcessDetailTask.execute();
246307
}
247308

309+
private void runOnUiThread(@NonNull Runnable runnable) {
310+
Activity activity = getActivity();
311+
if (activity == null) {
312+
return;
313+
}
314+
activity.runOnUiThread(runnable);
315+
}
316+
248317
private void setupRecyclerView() {
249318
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
250319
getActivity(),
@@ -256,24 +325,31 @@ private void setupRecyclerView() {
256325
mAdapter.setOnProcessDetailClickListener(new OnProcessDetailClickListener() {
257326
@Override
258327
public void onProcessDetailClick(@NonNull final ProcessDetail processDetail) {
259-
mActivityManager.killBackgroundProcesses(processDetail.getPackageName());
260-
Snackbar.make(
261-
mViews.recyclerView,
262-
R.string.snackbar_process_killed_message,
263-
Snackbar.LENGTH_LONG)
264-
.setAction(
265-
R.string.snackbar_process_killed_action,
266-
new View.OnClickListener() {
267-
@Override
268-
public void onClick(View v) {
269-
launchIntentForPackage(processDetail.getPackageName());
270-
}
271-
})
272-
.show();
328+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
329+
killProcessLegacy(processDetail.getPackageName());
330+
} else {
331+
killProcess(processDetail.getPackageName());
332+
}
273333
}
274334
});
275335
}
276336

337+
private boolean sendAdbCommand(String command) {
338+
Activity activity = getActivity();
339+
if (activity == null) {
340+
return false;
341+
}
342+
// Note that this makes use of ADB libraries from
343+
// https://github.com/tytydraco/LADB/tree/main/app/src/main/jniLibs
344+
String adbPath = activity.getApplicationInfo().nativeLibraryDir + "/libadb.so";
345+
String[] cmdLine = {"sh", "-c", adbPath + " " + command};
346+
try {
347+
return Runtime.getRuntime().exec(cmdLine).waitFor() == 0;
348+
} catch (IOException | InterruptedException e) {
349+
return false;
350+
}
351+
}
352+
277353
private void showProgress(boolean show) {
278354
mViews.spinner.setVisibility(show ? View.VISIBLE : View.GONE);
279355
}
4.9 MB
Binary file not shown.
3.57 MB
Binary file not shown.

app/src/main/jniLibs/x86/libadb.so

5.59 MB
Binary file not shown.
5.17 MB
Binary file not shown.

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
<!-- Killing process feedback -->
1414
<string name="snackbar_process_killed_message">Process killed if running</string>
1515
<string name="snackbar_process_killed_action">Go to app</string>
16+
<string name="snackbar_process_killing">Killing</string>
17+
<string name="snackbar_process_killing_failure_message">Failed to kill</string>
1618
<string name="error_application_can_not_be_launched">That application can not be launched</string>
1719

1820
</resources>

0 commit comments

Comments
 (0)