I am trying to run some GMock/GTest tests on Android. These all run fine, but there's no output, as GMock logs to stdout.
I've tried the following with no luck (likely because it's for the Dalvik VM, and they've done away with that in Android 5):
$ adb shell stop
$ adb shell setprop log.redirect-stdio true
$ adb shell start
When log.redirect-stdio is set to true, there is still no output from stdio to logcat.
I've also tried custom several streambuf implementations with std::cout.rdbuf to try to direct stdout to logcat with __android_log_print, but none of these have printed anything to logcat.
Has anyone successfully managed to redirect stdout to logcat in Android 5?
I can add more details (such as streambuf implementations I've tried) if needed.
This isn't really a solution to the problem of redirecting stdout to logcat, but I'm suggesting it as a workaround in case it helps someone.
You can redirect stdout to a file instead:
freopen("/data/data/com.your.package/files/out.txt", "w", stdout);
... // Call GMock which prints to the file instead
fclose(stdout)
We can then cat the file to see the logged test results. Sadly Android doesn't have tail so the logging isn't nicely available in real time. (Unless you're good at spamming cat)
Do it with the old Java way: (but I am using kotlin, can anyone suggest a cleaner version?)
documentation: System.setOut()
import java.io.OutputStream
import java.io.PrintStream
private const val TAG = "MyActivity"
class LogcatOutputStream: OutputStream(){
private var line_buffer: StringBuilder = StringBuilder()
override fun write(b: Int){
when(b){
'\n'.toInt() -> {
Log.i(TAG, line_buffer.toString())
line_buffer.setLength(0)
}
else -> line_buffer.append(b.toChar())
}
}
}
// put this somewhere in the code, like onCreate() as shown
class MainActivity: Activity(){
override fun onCreate(savedInstanceState: Bundle?){
// some other code
PrintStream(LoggerOutputStream()).let{
System.setOut(it)
System.setErr(it)
}
// some other code
}
}
// result:
println("Hello World") // which is effectively System.out.println in Java
// with have the below output in logcat
I/MyActivity(<pid>): Hello World
// as a reminder, you can filter logcat by tags
adb logcat MyActivity:D
// to only show logs tagged with 'MyActivity' (same value as 'TAG' above)
Related
Background
I want to try to send Unicode characters from the PC to the Android device via adb commands, as if they are being typed from a physical keyboard. Characters from various languages, for example, and not just English.
The problem
Such a thing is impossible using the commands I've found, as it seems to supports only a basic set of characters (probably only Ascii) :
adb shell "input keyboard text 'This goes to Android device'"
Because of this, I've decided to request it to be supported, here (please consider starring).
As a workaround, I thought that maybe I could develop an app that uses AccessibilityService and it would dispatch key events as if I'm typing via the device, and the PC would send such events using adb directly to the app via an Intent.
Thing is, after creating the app, I can't find which function I should use to do it.
What I've found
There are multiple things I've found:
onAccessibilityEvent - this is not for dispatching. It's only for getting events, which I don't think I will even need in this case.
getSoftKeyboardController - a function that can help with hiding the automatically shown keyboard, but that's about it...
dispatchGesture - a function that seems to be used only for dispatching touch events. It seems quite cool, but I don't see that it can handle keys.
performGlobalAction - seems promising, but sadly supports a very limited set of operations (back-key, home-key, etc...).
findFocus - I think I could use this and then dispatch a key event on what I get, but I'm not sure if this is a valid way to do it as I want to dispatch the event globally (plus maybe I would get null object, which means it might not be reliable). Not to mention that according to the options I see, it doesn't allow me to put the text right on the caret and that's it.
The question
Is it possible for AccessibilityService to dispatch a key event of Unicode characters, as if I type some text?
What's the best option to use for this?
This is an unconventional solution.
You can use the UI Automator framework to send Unicode characters through ADB to a focused text field like the input command does with ASCII characters (but fails with Unicode characters.)
First, implement an Android automation test that is capable of receiving broadcasts. Broadcasts will direct the test to do certain tasks. The implementation below will clear text and enter text using Base-64 or Unicode. I should not that the following can act like a background server until stopped.
AdbReceiver.kt
package com.example.adbreceiver
/*
* Test that runs with a broadcast receiver that accepts commands.
*
* To start the test:
* adb shell nohup am instrument -w com.example.adbreceiver.test/androidx.test.runner.AndroidJUnitRunner
*
* On Windows, the code page may need to be changed to UTF-8 by using the following command:
* chcp 65001
*
*/
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Base64
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import org.junit.Test
import org.junit.runner.RunWith
#RunWith(AndroidJUnit4::class)
#SdkSuppress(minSdkVersion = 18)
class AdbInterface {
private var mDevice: UiDevice? = null
private var mStop = false
private val ACTION_MESSAGE = "ADB_INPUT_TEXT"
private val ACTION_MESSAGE_B64 = "ADB_INPUT_B64"
private val ACTION_CLEAR_TEXT = "ADB_CLEAR_TEXT"
private val ACTION_STOP = "ADB_STOP"
private var mReceiver: BroadcastReceiver? = null
#Test
fun adbListener() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
if (mReceiver == null) {
val filter = IntentFilter(ACTION_MESSAGE)
filter.addAction(ACTION_MESSAGE_B64)
filter.addAction(ACTION_CLEAR_TEXT)
filter.addAction(ACTION_STOP)
mReceiver = AdbReceiver()
ApplicationProvider.getApplicationContext<Context>().registerReceiver(mReceiver, filter)
}
try {
// Keep us running to receive commands.
// Really not a good way to go, but it works for the proof of concept.
while (!mStop) {
Thread.sleep(10000)
}
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
fun inputMsg(s: String?) {
mDevice?.findObject(By.focused(true))?.setText(s)
}
internal inner class AdbReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_MESSAGE -> {
val msg = intent.getStringExtra("msg")
inputMsg(msg)
}
ACTION_MESSAGE_B64 -> {
val data = intent.getStringExtra("msg")
val b64 = Base64.decode(data, Base64.DEFAULT)
val msg: String
try {
msg = String(b64, Charsets.UTF_8)
inputMsg(msg)
} catch (e: Exception) {
}
}
ACTION_CLEAR_TEXT -> inputMsg("")
ACTION_STOP -> {
mStop = true
ApplicationProvider.getApplicationContext<Context>()
.unregisterReceiver(mReceiver)
}
}
}
}
}
Here is a short demo running on an emulator. In the demo, the first text "你好嗎? Hello?" is entered with adb using Base-64 encoding. The second text, "你好嗎? Hello, again?" is entered as a straight Unicode string.
Here are three Windows .bat file to manage the interface. There is not reason that these can't be ported to other OSes.
start.bat
Starts the instrumented test that receives the command broadcasts. This will run until it receives the "ADB_STOP" command.
rem Start AdbReceiver and disconnect.
adb shell nohup am instrument -w com.example.adbreceiver.test/androidx.test.runner.AndroidJUnitRunner
send.bat
Used for the demo to send Unicode text but can be easily generalized
rem Send text entry commands to AdbReceiver. All text is input on the current focused element.
rem Change code page to UTF-8.
chcp 65001
rem Clear the field.
adb shell am broadcast -a ADB_CLEAR_TEXT
rem Input the Unicode characters encode in Base-64.
adb shell am broadcast -a ADB_INPUT_B64 --es msg 5L2g5aW95ZeOPyBIZWxsbz8=
rem Input the Unicode characters withouth further encoding.
adb shell am broadcast -a ADB_INPUT_TEXT --es msg '你好嗎? Hello, again?'
stop.bat
Stops the instrumented test.
rem Stop AdbReceiver.
adb shell am broadcast -a ADB_STOP
For some reason, the code only works on API 21+. It doesn't error out on earlier APIs but just silently fails.
This is just a proof of concept and the code needs more work.
Project AdbReceiver is on GitHub.
How to Run (Windows)
Start an emulator.
Bring up the project in Android Studio.
Under java->com.example.adbreceiver (andoidTest) right click AdbInterface.
In the pop-up menu, click "Run". This will start the instrumented test.
Bring up any app on the emulator and set the cursor into a data entry fields (EditText).
In a terminal window, enter
adb shell am broadcast -a ADB_INPUT_TEXT --es msg 'Hello World!'
This should enter "Hello World!" into the text field.
This can also be accomplished from the command line. See the "start.bat"
file above on how to start the test and the "stop.bat" file on how to stop it.
Notes on UI Automator and Assessibility
I took a look under the hood at how UI Automator works. As the OP guessed, UI Automator does use assessibility services on Android. The AssessibilityNode is used to set text. In the posted code above, the inputMesg() function has the line:
mDevice?.findObject(By.focused(true))?.setText(s)
findObject() is in UiDevice.java which looks like this:
public UiObject2 findObject(BySelector selector) {
AccessibilityNodeInfo node = ByMatcher.findMatch(this, selector, getWindowRoots());
return node != null ? new UiObject2(this, selector, node) : null;
}
setText() can be found in _UiObject2.java` and starts off like this:
public void setText(String text) {
AccessibilityNodeInfo node = getAccessibilityNodeInfo();
// Per framework convention, setText(null) means clearing it
if (text == null) {
text = "";
}
if (UiDevice.API_LEVEL_ACTUAL > Build.VERSION_CODES.KITKAT) {
// do this for API Level above 19 (exclusive)
Bundle args = new Bundle();
args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
if (!node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)) {
// TODO: Decide if we should throw here
Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_SET_TEXT) failed");
}
} else {
...
So, accessibility services is integral to UI Automator.
Here is what I found based on my research on input command.
input keyevent KEYCODE_N
These KEYCODE are predefined in here
Logs:
Input : injectKeyEvent: KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_N, scanCode=0, metaState=0, flags=0x0, repeatCount=0, eventTime=25861196, downTime=25861196, deviceId=-1, source=0x101 }
Input : injectKeyEvent: KeyEvent { action=ACTION_UP, keyCode=KEYCODE_N, scanCode=0, metaState=0, flags=0x0, repeatCount=0, eventTime=25861196, downTime=25861196, deviceId=-1, source=0x101 }
To get more insight on above logs refer this source file.
Even switching the keyboard layout to other languages in the device, the keyevent only prints the ASCII characters.
Here are the resources that I found for key event in accessibility services:
In Android framework the accessibility service already dispatch the key Event. Refer here
FLAG_REQUEST_FILTER_KEY_EVENTS: If this flag is set the accessibility service will receive the key events before applications allowing it implement global shortcuts.
Note: Analysis based on Android 9.0
Not sure, if I exactly understand what you are looking for.
But, here are some resources that you can refer to perform actions on behalf of users using the Accessibility Service.
performAction()
performGlobalAction()
Taking Actions for User
Or in case you are looking to develop a completely new accessibility service, you can refer to this:
Developing an Accessibility Service for Android
PROBLEM:
Every time I run this python function in my monkeyrunner.py script, it opens a new background instance of (cmd, adb, and conhost). And so, in my automation script, if I have a loop that uses that 100 times, I'm going to see 100 of each cmd, adb, and conhost running in the background (I know this because I enter "ps" in powershell to get the list of processes.) The purpose of the function, if you're curious, is to look for logcat messages from the USB attached Android tablet, to see when processes are finished, so that the script knows when to command screen touches to move forward with automation testing.
action = "____"
waitTime = 1
def adb(logMessage, action):
start = time.time()
p = subprocess.Popen("adb logcat -v time", shell=True, cwd="C:\Users\<USERNAME>\AppData\Local\Android\sdk\platform-tools", stdout=subprocess.PIPE)
for line in p.stdout:
if logMessage in line:
print("Found message!")
break
pass
else:
continue
QUESTION:
How can I use "subprocess" to open adb WITHOUT opening a new instance each time? Is there a way to close the subprocess in the same function?
2 things.
adb logcat is a blocking call. It doesn't return unless you send it a SIGINT (ctrl +c). So inside a script you have to send it the "-d" flag. If you forget your script will keep waiting.
you can wait for a subprocess to complete with p.wait()
Therefore you can try this
action = "____"
waitTime = 1
def adb(logMessage, action):
start = time.time()
p = subprocess.Popen("adb logcat -d -v time", shell=True, cwd="C:\Users\<USERNAME>\AppData\Local\Android\sdk\platform-tools", stdout=subprocess.PIPE)
#p.wait()
for line in p.stdout:
if logMessage in line:
print("Found message!")
break
pass
else:
continue
This will ensure that every adb session launched to look at logact terminates properly and its output is looked at only after it returns
EDIT: You are absolutely right, p.wait() isn't needed and is actually wrong! because the logcat writes more than 4kb to stdout in yourcase. You could try to use commuinicate instead to help with that
I figured it out. To prevent a new session of adb from opening in the background per each time this function is called, all I had to do is place the "p" variable outside the function.... like this...
p = subprocess.Popen("adb logcat -v time", shell=True, cwd="C:\Users\<USERNAME>\AppData\Local\Android\sdk\platform-tools", stdout=subprocess.PIPE)
action = "____"
waitTime = 1
def adb(logMessage, action):
start = time.time()
for line in p.stdout:
if logMessage in line:
print("Found message!")
break
pass
else:
continue
There is still the issue of a single adb session that opens and does not close after the script is run. So, now, instead of 180 sessions (or more) opening, there is a single one. If I find out how to close the session I will update this ticket.
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.
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!
Is it possible to read information being sent over LogCat in python?
I have a program that is written in java.
Every draw frame it sends tag:"Fps: " message: number
I would like this message to fire an event that I can catch in my python script so I can draw a fps-meter.
Take a look at subprocess. The following code was adapted from Stefaan Lippens
import Queue
import subprocess
import threading
class AsynchronousFileReader(threading.Thread):
'''
Helper class to implement asynchronous reading of a file
in a separate thread. Pushes read lines on a queue to
be consumed in another thread.
'''
def __init__(self, fd, queue):
assert isinstance(queue, Queue.Queue)
assert callable(fd.readline)
threading.Thread.__init__(self)
self._fd = fd
self._queue = queue
def run(self):
'''The body of the tread: read lines and put them on the queue.'''
for line in iter(self._fd.readline, ''):
self._queue.put(line)
def eof(self):
'''Check whether there is no more content to expect.'''
return not self.is_alive() and self._queue.empty()
# You'll need to add any command line arguments here.
process = subprocess.Popen(["logcat"], stdout=subprocess.PIPE)
# Launch the asynchronous readers of the process' stdout.
stdout_queue = Queue.Queue()
stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue)
stdout_reader.start()
# Check the queues if we received some output (until there is nothing more to get).
while not stdout_reader.eof():
while not stdout_queue.empty():
line = stdout_queue.get()
if is_fps_line(line):
update_fps(line)
Of course, you'll need to write the is_fps_line and update_fps functions yourself.
I would redirect adb logcat to your python script. This would look like:
$ adb logcat | python yourscript.py
Now you can read from logcat on sys.stdin and parse it however you like.