ADB command from macOS application with special characters - android

I want to pull a file from my android device through an adb command from my macOS application.
Everything works perfect with the code below, except when the name of the file I want to pull contains special characters like german umlauts (äöüÄÖÜ).
I get this error:
adb: error: failed to stat remote object '/storage/emulated/0/Download/Böse': No such file or directory.
But when I use the command adb pull /storage/emulated/0/Download/Böse ~/Desktop from within the Terminal.app, the file will be pulled to my computer.
The strange thing here is that if I copy the substring /storage/emulated/0/Download/Böse from the Xcode console output, the command is also not working within the Terminal.app until I delete the ö and replace it with an ö from my keyboard input.
I tried replacing the ö with the unicode representation \u{00f6}, but this has no effect (but the console output still shows an ö but the 'wrong' encoded one.
// Configure task.
let task = Process()
task.launchPath = "~/Library/Android/sdk/platform-tools/adb"
task.arguments = ["pull", "/storage/emulated/0/Download/Böse", "~/Desktop"]
// Configure pipe.
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
// Run task.
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
// adb: error: failed to stat remote object '/storage/emulated/0/Download/Böse': No such file or directory
print(output)
I found the following in the documentation, how the Process handles the arguments that I provide:
The NSTask object converts both path and the strings in arguments to appropriate C-style strings (using fileSystemRepresentation) before passing them to the task via argv[] . The strings in arguments do not undergo shell expansion, so you do not need to do special quoting, and shell variables, such as $PWD, are not resolved.
It seems like I am not the only one with this problem, and I found this workaround:
How to work around NSTask calling -[NSString fileSystemRepresentation] for arguments, but I was not able to make it work with Swift.

As a workaround I am now writing my adb command to a file and execute it from a bash command in my application.
let source = "/storage/emulated/0/Download/Böse"
let destination = "~/Desktop"
guard let uniqueURL = URL(string: destination + "/" + ProcessInfo.processInfo.globallyUniqueString) else { return }
// Write command to file
let scriptContent = "#!/bin/bash\n~/Library/Android/sdk/platform-tools/adb pull -a \"" + source + "\" \"" + destination + "\""
try? scriptContent.write(to: uniqueURL, atomically: false, encoding: .utf8)
// Configure task.
let task = Process()
task.environment = ["LC_ALL": "de_DE.UTF-8", "LANG": "de_DE.UTF-8"]
task.launchPath = "/bin/bash"
task.arguments = [uniqueURL.path]
// Configure pipe.
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
try? task.run()
// Run task.
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
print(output)
Even though this is working for now, it is not a satisfactory solution as it is not very elegant and not efficient too, so any improvements or better answers are welcome.

Related

Passing string in stdin

I want to create an AVD (Android Virtual Device) through command line in python. For that, I need to pass a string n to the stdin. I have tried the following
emulator_create = str(subprocess.check_output([android,'create', 'avd', '-n', emulator_name, '-t', target_id, '-b', abi],stdin=PIPE))
emulator_create.communicate("n")
but it raises the following error
raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command '['/home/fahim/Android/Sdk/tools/android', 'create', 'avd', '-n', 'samsung_1', '-t', '5', '-b', 'android-tv/x86']' returned non-zero exit status 1
Process finished with exit code 1
What can I do?
There's something not working with your example. subprocess.check_output() returns the output from the child process you want to execute, not a handle to this process. In other words you get a string object (or maybe a bytes object) which you cannot use to manipulate the child process.
Probably what happens is that your script, using subprocess.check_output(), will execute the child process and wait until it is finished before continuing. But since you are never able to communicate with it, it will finish with a non-zero return value which will raise the subprocess.CalledProcessError
Now, using grep as an example of a command that waits on the standard input to execute something (since I don't have an Android Virtual Device creator installed) you could do this:
#!/usr/bin/env python2.7
import subprocess
external_command = ['/bin/grep', 'StackOverflow']
input_to_send = '''Almost every body uses Facebook
You can also say that about Google
But you can find an answer on StackOverflow
Even if you're an old programmer
'''
child_process = subprocess.Popen(args=external_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
universal_newlines=True)
stdout_from_child, stderr_from_child = child_process.communicate(input_to_send)
print "Output from child process:", stdout_from_child
child_process.wait()
It will print "Output from child process: But you can find an answer on StackOverflow", which is the output from grep.
In this example, I have
Used the class subprocess.Popen to create an handle to the child process
Setting arguments stdin and stdout with the value subprocess.PIPE to enables us to communicate later on with this process.
Used its .communicate() method to send a string to its standard input. In the same step, I retrieved its standard output and standard error output.
Printed the standard output retrieved in the last step (just so to show that it is actually working)
Waited that this child process is finished
In Python 3.5, it's even simpler:
#!/usr/bin/env python3.5
import subprocess
external_command = ['/bin/grep', 'StackOverflow']
input_to_send = '''Almost every body uses Facebook
You can also say that about Google
But you can find an answer on StackOverflow
Even if you're an old programmer
'''
completed_process_result = subprocess.run(args=external_command,
input=input_to_send,
stdout=subprocess.PIPE,
universal_newlines=True)
print("Output from child process:", completed_process_result.stdout)
In this example, I have:
Used the module function subprocess.run() to execute the command.
The input argument is the string we send to the standard input of the child process
The return value is used later on to retreive the output of the child process
Now you have to adapt this code to your situation.

Android Logcat very strange behavior when getting empty string

So I came across something strange that made me loose some time. I have been trying to print the content of an ArrayList containing string elements, sometimes, an element might contain an empty string, which is fine and absolutely my intention.
So I have something like this:
List<String> l = new ArrayList<String>();
//adding strings in l, sometimes it's an empty string
for (int i=0; i < l.size(); i++) {
Log.w("element in l : ", l.get(i));
}
So here, when the loop is gonna hit the empty string, logcat is simply NOT going to print it out BUT (and here is the root of my confusion), if you have a message following the one that failed to display, suddenly the failed message is going to show up as if it contained the new logcat message. For example if you try logging an empty string like this
Log.w(TAG, <empty string here>);
Logcat is going to output nothing at first, then, when it has a NEW message to display this is what it prints out (in this case the new message is some warning about AudioTrack):
08-21 17:06:02.265 13047-13047/company.myapp W/TAG﹕ [ 08-21 17:06:05.411 766: 937 W/AudioTrack ]
AUDIO_OUTPUT_FLAG_FAST denied by client
I'm interested in knowing how this happens, maybe it can help someone else not getting super confused like I did. I suppose trying to log an empty string triggers some kind of buffer that sits there until it gets something to print, is this a bug?
That is an interesting question. I just tried this in LogRabbit and am able to see the same result.
I took a quick browse through the android source and see that Log.W(...) ends up in native code and getting handled in logd_write.c
This basically writes the data to /dev/log/main (or one of the other logs)
You can get those logs like this:
adb pull /dev/log/events .
adb pull /dev/log/main .
adb pull /dev/log/radio .
adb pull /dev/log/system .
You will need to press cntl-C otherwise the copy will happen forever.
Looking in the raw log in /dev/log/main I see the message does get logged:
<8b>F×U^_<8c>^Y^U^Emfl_MessageList^#Before Empty^#^R^#^#^#!z^#^#!z^#^#
<8b>F×U^_<8c>^Y^U^Emfl_MessageList^#^#^]^#^#^#!z^#^#!z^#^#
<8b>F×U^_ <8c>^Y^U^Emfl_MessageList^#After Empty^#7^#^#^#^#^E^#^#^Z^E^#^#
That gets decoded by struct found in logger.h So I think this is a problem in adb. pull the source code from here: (looks like quite a few of undocumented commands there)
This is the primary function
static int logcat(TransportType transport, const char* serial, int argc, const char** argv) {
char* log_tags = getenv("ANDROID_LOG_TAGS");
std::string quoted = escape_arg(log_tags == nullptr ? "" : log_tags);
std::string cmd = "shell:export ANDROID_LOG_TAGS=\"" + quoted + "\"; exec logcat";
if (!strcmp(argv[0], "longcat")) {
cmd += " -v long";
}
--argc;
++argv;
while (argc-- > 0) {
cmd += " " + escape_arg(*argv++);
}
return send_shell_command(transport, serial, cmd);
}
Looking in there I see that all logcat does is basically this:
adb shell
> exec logcat
So I think the root of the problem is in logcat itself. Logcat.cpp calls into log_read.c
Based on my quick read through things what I think is happening is the message is not terminated properly. The empty message does not show up until another message is appended and the first message overruns and shows the second message because it has the appropriate termination.

Storing android adb shell screencap as variable

Making an app at the moment for my personal use (rooted) and it requires getting certain pixels colors from the screen. I was trying to accomplish this through the Runtime.
Process p = Runtime.getRuntime().exec("screencap");
p.waitFor();
InputStream is = p.getInputStream()
BitmapFactory.decodeStream(is);
and I get factory returned null.
but if I dump the process to my sd card through adb -d shell screencap /sdcard/ss.dump and access it from my app
BitmapFactory.decodeFile("/sdcard/ss.dump");
all goes well.
So it there anyway to dump the stream straight into BitmapFactory within my app?
Thanks SO and please excuse the generally laziness/shortcuts of the example code.
This might help if not too far off your intended path. (I think you are using node / javascript). I spawned the ADB.EXE command producing a stream (and being 'jailed' on Windows the program must transform the stream to account for linefeed ending differences. So with that, I have working the following:
exports.capture = function(filename) {
// you'll need to map your requirement (decodeStream) instead
// of streaming to a file
var strm = fs.createWriteStream(path);
var cv = new Convert();
cv.pipe(strm);
var capture = spawn(cmd, args);
capture.stdout.on('data', function(data) {
cv.write(data);
});
capture.stdout.on('exit', function(data) {
cv.end();
});
}
To explain the process, spawn is running the ADB command, on windows, CR-LF are inserted (being a PNG stream), and stream is chunked / piped through a fs-transformation. Others on the web have described the process as adb.exe shell screencap -p | sed 's/\r$//' > output.file. And it does work. To be clear the conversion is CR-CR-LF => LF for us window jailed birds. And if you don't want to implement a SED and not use javascript regular expressions converting binary->strings->binary, you may follow my path. There is probably a simpler way, I just don't know what it is...
So, Convert() is an object with methods that converts the byte stream on the fly.
See the codewind blog link is: http://codewinds.com/blog/2013-08-20-nodejs-transform-streams.html to build your convert.
When using screencap from inside an app you must use su, i.e. root. When you do this via adb it runs under a different user id, which has more permissions than a normal Android app.
There are several examples how to use screencap, e.g. here.

var exec = require('child_process').exec; callbacks for commands that run continuously

I am trying to write an interface for the adb logcat command with node.js, and need some help having my function do continuous and asynchronous callbacks. This command generates constant output that I want to send to the callback function as it is generated (not after I let it run for awhile and then stop it). Is this possible with node.js, and if so how do I do it?
Here is my code so far
exports.logcat = function(options, callback){
var logcatCmd = "adb logcat" + options;
console.log("sending command: " + logcatCmd);
exec(logcatCmd, function(error, stdout, stderr){
if(error !== null){
console.log("encountered error = " + error);
}
callback(stdout);
});
};
In short, no; you cannot watch shell commands for new outputs, in the same way you can stream data from a changing file or socket.
If it generates constant output, it's more likely it just reads from some log file and prints
it on screen. You should figure out which file it's getting the data from, and use fs.watch with readableStream to continuously get the stream of data.

Launch an SL4A script from an SL4A script

I would like to start a background SL4A script (on a remote device) from within a different SL4A script. I can launch a script from a terminal by running something like this:
$ am start -a \
com.googlecode.android_scripting.action.LAUNCH_BACKGROUND_SCRIPT -n \
com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher -e \
com.googlecode.android_scripting.extra.SCRIPT_PATH /sdcard/sl4a/scripts/main.py
I can't translate this into a startActivity call in Python.
The answer to a different question on opening a Twitter client works nicely, but I don't know how to extend that code. For example, how would you add a script path, and where would you put the line com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher?
This function will launch any SL4A script from inside another one.
The first argument should be a path to the script you want to launch. The script can be in any language you have an interpreter installed for.
The second argument is optional and should be a bool. It defaults to False. It controls whether the terminal will be visible, so you can see output and errors. It does not effect whether the script has a UI or not.
from android import Android
droid = Android()
def launch_script(path, visible=False):
visibilty = 'FORE' if visible else 'BACK'
activity = 'com.googlecode.android_scripting.action.LAUNCH_{0}GROUND_SCRIPT'.format(visibilty)
extras = {'com.googlecode.android_scripting.extra.SCRIPT_PATH': path}
packagename = 'com.googlecode.android_scripting'
classname = 'com.googlecode.android_scripting.activity.ScriptingLayerServiceLauncher'
intent = droid.makeIntent(activity, None, None, extras, None, packagename, classname).result
droid.startActivityIntent(intent)
There's a gist for this code here.
After many, many failed attempts, I now have this working in Ruby - I had an easier time generating the JSON extras this way than in Python.
Important!
In the command-line version, you call on "com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher"
From within a script, this is called as "com.googlecode.android_scripting.activity.ScriptingLayerServiceLauncher", without the slash. Leaving in the slash crashes sl4a.
[code]
require 'android'
require 'json/pure'
d=Android.new
script = '/sdcard/sl4a/scripts/YOUR_SCRIPT'
data = {"com.googlecode.android_scripting.extra.SCRIPT_PATH"=>script}
extras = JSON.generate(data)
d.startActivity('com.googlecode.android_scripting.action.LAUNCH_BACKGROUND_SCRIPT','','',data,true,'com.googlecode.android_scripting','com.googlecode.android_scripting.activity.ScriptingLayerServiceLauncher')
[/code]
I hope this helps!

Categories

Resources