43

I am working on an Android application that depends on an ELF binary: our Java code interacts with this binary to get things done. This runtime needs to be started and terminated on Application startup and application exit / on demand.

Questions:

  1. I am assuming that we will be able to execute this binary using the Runtime.exec() API. Is there any constraints as to where I need to be putting my library in the folder structure? How would the system runtime locate this executable? Is there some sort of class path setting?

  2. Since the application has dependencies on this Runtime, I was thinking of wrapping it around a service so that it can be started or stopped as required. What is the best way to handle such executables in Android project?

  3. What are other alternatives, assuming that I do not have source code for this executable?

Please advice.

Thanks.

Samuh
  • 36,316
  • 26
  • 109
  • 116

4 Answers4

51

1) No, there should be no constrains, besides those that access system files and thus require root. The best place would be straight to /data/data/[your_package_name] to avoid polluting elsewhere.

2) A very thorough discussion about compiling against native libraries can be found here: http://www.aton.com/android-native-libraries-for-java-applications/ . Another option is a cross-compiler for arm (here is the one used to compile the kernel, it's free: http://www.codesourcery.com/sgpp/lite/arm ). If you plan to maintain a service that executes your cammand, be warned that services can be stopped and restarted by android at any moment.

3) Now, if you don't have the source code, I hope that your file is at least compiled as an arm executable. If not, I don't see how you could even run it.


You will execute the file by running the following commands in your java class:

String myExec = "/data/data/APPNAME/FILENAME";
Process process = Runtime.getRuntime().exec(myExec);
DataOutputStream os = new DataOutputStream(process.getOutputStream());
DataInputStream osRes = new DataInputStream(process.getInputStream());

I know nothing about your executable, so you may or may not need to actually get the inputStream and outputStream.


I am assuming that running adb to push the binary file is out of the question, so I was looking for a neat way to package it. I found a great post about including an executable in your app. Check it out here: http://gimite.net/en/index.php?Run%20native%20executable%20in%20Android%20App

The important part is this one (emphasis mine):

From Android Java app, using assets folder

  • Include the binary in the assets folder.
  • Use getAssets().open(FILENAME) to get an InputStream.
  • Write it to /data/data/APPNAME (e.g. /data/data/net.gimite.nativeexe), where your application has access to write files and make it executable.
  • Run /system/bin/chmod 744 /data/data/APPNAME/FILENAME using the code above.
  • Run your executable using the code above.

The post uses the assets folder, insted of the raw folder that android suggests for static files:

Tip: If you want to save a static file in your application at compile time, save the file in your project res/raw/ directory. You can open it with openRawResource(), passing the R.raw. resource ID. This method returns an InputStream that you can use to read the file (but you cannot write to the original file).

To access the data folder, you can follow the instructions here: http://developer.android.com/guide/topics/data/data-storage.html#filesInternal Also, there's the File#setExecutable(boolean); method that should works instead of the shell command.

So, putting everything together, I would try:

InputStream ins = context.getResources().openRawResource (R.raw.FILENAME)
byte[] buffer = new byte[ins.available()];
ins.read(buffer);
ins.close();
FileOutputStream fos = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(buffer);
fos.close();


File file = context.getFileStreamPath (FILENAME);
file.setExecutable(true);

Of course, all this should be done only once after installation. You can have a quick check inside onCreate() or whatever that checks for the presence of the file and executes all this commands if the file is not there.

Let me know if it works. Good luck!

User
  • 14,131
  • 2
  • 40
  • 59
Aleadam
  • 40,203
  • 9
  • 86
  • 108
  • @Aleadam: Thanks for your reply. Yeah, the source code is compiled as ARM executable and I have tried executing it on a rooted Android phone from command line and it worked. What I need is a way to package this executable inside my Android project, copy it to the internal file system (/data/data/) and then execute it from there. – Samuh Apr 13 '11 at 01:01
  • Cool, then I think you're in good shape. Do you need to keep that executable running all the time or it will just be doing a specific, quick job? – Aleadam Apr 13 '11 at 03:45
  • 1
    @Aleadam: What I have been trying is consistent with what you've said. But the problem with above mentioned approach is that it requires root permissions. And of course the SDK doesn't support running executable from Java code (for e.g. `setExecutable` on a `File` returns `NoSuchMethodError`). – Samuh Apr 15 '11 at 13:31
  • Thanks again for such a detailed answer! – Samuh Apr 15 '11 at 13:38
  • @Samuh you're welcome. `chmod` should not require root if you're changing the file inside the app data folder... an alternative approach is to use the `FilePermission` class (as explained in this answer: http://stackoverflow.com/questions/571783/file-permissions-in-android/571845#571845) – Aleadam Apr 15 '11 at 13:54
  • @Aleadam: You are correct chmod does not require root permission but even after I do that I am getting Permission denied exception. – Samuh Apr 15 '11 at 21:44
  • Did you try `chmod 777`? I don't know who's the real owner of the file in this case. – Aleadam Apr 15 '11 at 21:59
  • @Aleadam: I would retry this but am really skeptical if this would work on a non-rooted phone though. Your answer is helpful enough and I wish I am wrong here and that the code works! :) You are getting this bounty. – Samuh Apr 17 '11 at 17:19
  • @Aleadam: It works on a non-rooted phone as long as I keep myself within the boundaries of `/data/data//files/`. I need to figure out now how to keep it running when required without burning the battery too much. What do you suggest? Thanks again! cheers! – Samuh Apr 18 '11 at 19:09
  • Great you got that working! I'd suggest you, now that you have that part sorted out, to start a new question describing a little bit what this executable does, so you can get better suggestions. – Aleadam Apr 18 '11 at 19:21
  • Thanks for a great answer. Suppose the code for the native executable was in the project's jni directory. Can anyone suggest a way to have the ndk-build automatically copy the output into `res/raw`? – zmb Jul 21 '13 at 16:25
  • Hi, I am trying to execute .exe file but it does not show any output.i am following the above code.I am getting process.getInputstream() returns -1.any one suggest me how to execute .exe in android without using NDK? – Rajesh Apr 15 '18 at 17:15
  • That's no longe true for Android 10, see my answer bellow for details. – gordinmitya Nov 07 '19 at 12:22
14

Here is a complete guide for how to package and run the executable. I based it on what I found here and other links, as well as my own trial and error.

1.) In your SDK project, put the executable file in your /assets folder

2.) Programmatically get the String of that files directory (/data/data/your_app_name/files) like this

String appFileDirectory = getFilesDir().getPath();
String executableFilePath = appFileDirectory + "/executable_file";

3.) In your app's project Java code: copy the executable file from /assets folder into your app's "files" subfolder (usually /data/data/your_app_name/files) with a function like this:

private void copyAssets(String filename) {

AssetManager assetManager = getAssets();

InputStream in = null;
OutputStream out = null;
Log.d(TAG, "Attempting to copy this file: " + filename); // + " to: " +       assetCopyDestination);

try {
    in = assetManager.open(filename);
    Log.d(TAG, "outDir: " + appFileDirectory);
    File outFile = new File(appFileDirectory, filename);
    out = new FileOutputStream(outFile);
    copyFile(in, out);
    in.close();
    in = null;
    out.flush();
    out.close();
    out = null;
} catch(IOException e) {
Log.e(TAG, "Failed to copy asset file: " + filename, e);
} 

Log.d(TAG, "Copy success: " + filename);
}

4.) Change the file permissions on executable_file to actually make it executable. Do it with Java calls:

File execFile = new File(executableFilePath);
execFile.setExecutable(true);

5.) Execute the file like this:

Process process = Runtime.getRuntime().exec(executableFilePath);

Note that any files referred to here (such as input and output files) must have their full path Strings constructed. This is because this is a separate spawned process and it has no concept of what the "pwd" is.

If you want to read the command's stdout you can do this, but so far it's only working for me for system commands (like "ls"), not the executable file:

BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
int read;
char[] buffer = new char[4096];
StringBuffer output = new StringBuffer();
while ((read = reader.read(buffer)) > 0) {
    output.append(buffer, 0, read);
}
reader.close();
process.waitFor();

Log.d(TAG, "output: " + output.toString());

matteo411
  • 390
  • 2
  • 6
8

For executing binary file starting from Android 10 it's only possible from read-only folder. It means that you should pack binary with your app. Android doc

  1. Put android:extractNativeLibs="true" into AndroidManifest;
  2. Put your binary to src/main/resources/lib/* directory, where * – stands for architecture of CPU, for instance armeabi-v7a;
  3. Use code like this for executing:

    private fun exec(command: String, params: String): String {
    try {
        val process = ProcessBuilder()
            .directory(File(filesDir.parentFile!!, "lib"))
            .command(command, params)
            .redirectErrorStream(true)
            .start()
        val reader = BufferedReader(
            InputStreamReader(process.inputStream)
        )
        val text = reader.readText()
        reader.close()
        process.waitFor()
        return text
    } catch (e: Exception) {
        return e.message ?: "IOException"
    }
    }
    

Here is discussion with answer from android team on reddit.

gordinmitya
  • 967
  • 10
  • 10
  • 1
    This doesn't work for me. I've done all of the steps listed, and also tried setting `android.bundle.enableUncompressedNativeLibraries=false` in gradle.properties but still on my device, I end up with an empty `/lib` folder. – mjaggard Dec 12 '19 at 00:38
  • @mjaggard how do you check your /lib folder? Did you tried to run the code with ProcessBuilder? – gordinmitya Dec 14 '19 at 09:00
  • I've been checking using ls on the device. I'm pretty sure changing the code won't help unless something does static analysis on the code in order to work out what to copy which seems remarkably unlikely. – mjaggard Dec 15 '19 at 11:24
  • Regular executables from `src/main/jniLibs/$ABI/` don't get included in the APK. They are included if the name ends in `.so`, for example `src/main/jniLibs/$ABI/doSomethingBinary.exe.not.so`. Sadly, I couldn't get them extracted on the device. Normal `.so`'s are extracted to libs folder, but `.exe.not.so`'s are filtered out during install. – Vilius Sutkus '89 Jan 07 '20 at 00:08
  • @Vilius1989 You can place your executables in your `assets` folder then it will not be filtered out. I tested it by placing my executables in a zip folder and placing that zip in my `assets` folder, then I extract the executable from the zip folder at runtime. Note - I do not believe this will work with `.so` executables. – HB. Feb 06 '20 at 05:19
  • 1
    @HB any extraction at runtime involves writing the file which means that it can't be executed in Android 10 surely? – mjaggard Feb 26 '20 at 22:29
  • @mjaggard Why can't a file be written in Android 10? You can open a `FileInputStream` with `ContentResolver` and write the bytes to a `OutputStream`. – HB. Feb 27 '20 at 02:59
  • 1
    @HB yes, you can write it but you can't then execute it. Android 10 implements https://en.wikipedia.org/wiki/W%5EX – mjaggard Feb 27 '20 at 07:35
  • I had to replace the directory (for Samsung S7 Edge with Android 8 installed) with `File(File(packageCodePath).parentFile!!, "lib/arm64")` (no idea why it's `arm64` and not `arm64-v8a`). Don't know if it depends on Android version or something else... – Alexey Romanov Jul 28 '20 at 12:07
  • And the command has to be not just the executable's name, but `"./name"` or the full path. – Alexey Romanov Jul 28 '20 at 12:51
  • Some additional information in https://stackoverflow.com/a/63174663/9204. – Alexey Romanov Jul 30 '20 at 14:11
  • In a dev build this works, however in a release build it seems the executable is not found in the lib/ABI folder, any ideas? – MandelDuck Aug 18 '20 at 22:31
1

I've done something like this using the NDK. My strategy was to recompile the program using the NDK and write some wrapper JNI code that called into the program's main function.

I'm not sure what the lifecycle of NDK code is like. Even services that are intended to be long-running can be started and stopped by the system when convenient. You would probably have to shutdown your NDK thread and restart it when necessary.

Matthew
  • 44,826
  • 10
  • 98
  • 87
  • I'd agree with this - a JNI wrapper is the normal way ahead. If possible, I'd also re-design so that the ELF isn't a normal daemon - long-running processes are bad for battery life. – Phil Lello Apr 07 '11 at 16:15
  • I do not have source code for this binary; just the executable for a particular machine architecture. :-( – Samuh Apr 07 '11 at 17:04