python how to keep one thread executing till other threading finished - android

I hope to record app(eg.com.clov4r.android.nil) the CPU occupancy when I operate the app(eg.doing monkey test) and finish recording when I eixt the app(eg.finishing monkey test). How to realise it with python?
Some codes:
packagename = 'com.clov4r.android.nil'
cmd1 = 'adb shell top -d 5 | grep com.clov4r.android.nil'
cmd2 = 'adb shell monkey -v -p com.clov4r.android.nil --throttle 500 --ignore-crashes --ignore-timeouts --ignore-security-exceptions --monitor-native-crashes -s 2345 100'
t1 = threading.Thread(target=subprocess.call(cmd1, stdout=open(r'123.txt', 'w')))
t2 = threading.Thread(target=subprocess.call(cmd2))

You can use Thread.join() :
import threading, time
def worker():
time.sleep(5)
t = threading.Thread(target=worker)
t.start()
t.join()
print('finished')

Events are a good way to communicate between threads (http://docs.python.org/2/library/threading.html#event-objects). However, the other problem you will have is that the top command will essentially run forever. I would do something like this:
def run_top(event, top_cmd):
s = subprocess.Popen(top_cmd, stdout=open('123.txt', 'w'))
event.wait() # Wait until event is set, then kill subprocess
s.kill()
def run_monkey(event, monkey_cmd):
subprocess.call(monkey_cmd)
event.set() # Once we're finished set the event to tell the other thread to exit
event = threading.Event()
threading.Thread(target=run_top, args=(event, your_top_command)).start()
threading.Thread(target=run_monkey, args=(event, your_monkey_command)).start()
There might be a way to kill the thread as well but that's pretty ugly, this way is much more controlled.
I would also say run_monkey() doesn't need to be run in a thread, but not sure what other code you have that may require it.

Related

adb command for call state details

I would like to get an adb command with a response of a code that map to current call state
the call state I mean are those in following link
https://developer.android.com/reference/android/telecom/Call.html#STATE_ACTIVE
those values are more representative and getting those values in command shell upon executing the adb command will be very helpful for me
I have only managed to get them on a log as per following command
adb logcat -d | findstr -i InCallFragment.setCallState
but I couldnot get the state value as a response of any adb command
Any help will be much appreciated
Thanks
for more illustration
please connect a phone to the PC , do a phone call and end it
use the above command to dump the buffer
refer to the state value
You can use adb shell service call telecom [code] command. The codes for getCallState() will be different depending on the Android version:
6.0.1: 26
7.0.0: 27
7.1.0: 27
7.1.2: 27
8.0.0: 29
8.1.0: 29
I have achieved what you want to do by modifying a custom ROM (LineageOS) and adding an android.util.Log line to print every state.
In my case I modified class:
Call
frameworks/opt/telephony/src/java/com/android/internal/telephony/Call.java
And what I did is inside getState(...) method, adding this line:
Log.i(myTAG, "getState state->" + mState.name());
With this what I have to do is search for myTAG in adb logcat.
I think otherwise you wont be able to do it...
You can dumpsys telecomm service:
adb shell dumpsys telecom
CallsManager:
mCalls:
[TC#7, ACTIVE, com.android.phone/com.android.services.telephony.TelephonyConnectionService, tel:***, A, childs(0), has_parent(false), [Capabilities: CAPABILITY_HOLD CAPABILITY_SUPPORT_HOLD CAPABILITY_MUTE CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO], [Properties:]]
mCallAudioManager:
All calls:
TC#7
Active dialing, or connecting calls:
TC#7
Ringing calls:
Holding calls:
Foreground call:
[TC#7, ACTIVE, com.android.phone/com.android.services.telephony.TelephonyConnectionService, tel:***, A, childs(0), has_parent(false), [Capabilities: CAPABILITY_HOLD CAPABILITY_SUPPORT_HOLD CAPABILITY_MUTE CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO], [Properties:]]
mTtyManager:
mCurrentTtyMode: 0
mInCallController:
mInCallServices (InCalls registered):
.
.
Call TC#7 [2018-06-05 14:38:41.505](MO - outgoing)
To address: tel:***
14:38:41.508 - CREATED:PCR.oR#DMA
14:38:41.511 - SET_CONNECTING (ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}, [8c3d1caa626a79d75b154221ea94852a62fee7b3], UserHandle{0}):PCR.oR#DMA
14:38:41.847 - AUDIO_ROUTE (Leaving state QuiescentEarpieceRoute):PCR.oR->CAMSM.pM_2001->CARSM.pM_SWITCH_FOCUS#DMA_2_2
14:38:41.847 - AUDIO_ROUTE (Entering state ActiveEarpieceRoute):PCR.oR->CAMSM.pM_2001->CARSM.pM_SWITCH_FOCUS#DMA_2_2
14:38:43.442 - BIND_CS (ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}):NOCBIR.oR#DMU
14:38:43.519 - CS_BOUND (ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}):SBC.oSC#DMY
14:38:43.519 - START_CONNECTION (tel:***):SBC.oSC#DMY
14:38:43.703 - CAPABILITY_CHANGE (Current: [[ sup_hld mut !v2a]], Removed [[]], Added [[ sup_hld mut !v2a]]):CSW.hCCC#DMg
14:38:43.706 - SET_DIALING (successful outgoing call):CSW.hCCC#DMg
14:38:47.560 - SET_ACTIVE (active set explicitly):CSW.sA#DNM
14:38:47.639 - CAPABILITY_CHANGE (Current: [[ hld sup_hld mut !v2a]], Removed [[]], Added [[ hld]]):CSW.sCC#DNY
Timings (average for this call, milliseconds):
bind_cs: 77.00
outgoing_time_to_dialing: 187.00

How can I prevent new instances of background processes when calling adb in python subprocess module

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.

How to launch android app through python

I want to run any app (say Settings) after rebooting tablet. Can I use os.system or do I have to use other methods.
import os,time
for i in range(0,3):
os.system("adb reboot")
time.sleep(60)
Yes, you can use os.system to execute ADB commands. If you want to validate the command executed successfully, take a look at the check_output(...) function which is apart of the subprocess library. This code snipet is how I choose to implement the check_output function. For the full code look here.
def _run_command(self, cmd):
"""
Execute an adb command via the subprocess module. If the process exits with
a exit status of zero, the output is encapsulated into a ADBCommandResult and
returned. Otherwise, an ADBExecutionError is thrown.
"""
try:
output = check_output(cmd, stderr=subprocess.STDOUT)
return ADBCommandResult(0,output)
except CalledProcessError as e:
raise ADBProcessError(e.cmd, e.returncode, e.output)
To launch an application you can use the command am start -n yourpackagename/.activityname. To launch the Settings App, run adb shell am start -n com.android.settings/com.android.settings.Settings. This stackoverflow question shows you in detail the options you can use to start the application via a command line intent.
Other tips:
I created an ADB wrapper written in python along with a few other python utilities that may aid in what you are trying to accomplish. For example, instead of calling time.sleep(60) to wait for the reboot, you use adb to poll the status of the property sys.boot_completed and once the property is set the device has finished booting and you can launch any application. Below is a reference implementation you can use.
def wait_boot_complete(self, encryption='off'):
"""
When data at rest encryption is turned on, there needs to be a waiting period
during boot up for the user to enter the DAR password. This function will wait
till the password has been entered and the phone has finished booting up.
OR
Wait for the BOOT_COMPLETED intent to be broadcast by check the system
property 'sys.boot_completed'. A ADBProcessError is thrown if there is an
error communicating with the device.
This method assumes the phone will eventually reach the boot completed state.
A check is needed to see if the output length is zero because the property
is not initialized with a 0 value. It is created once the intent is broadcast.
"""
if encryption is 'on':
decrypted = None
target = 'trigger_restart_framework'
print 'waiting for framework restart'
while decrypted is None:
status = self.adb.adb_shell(self.serial, "getprop vold.decrypt")
if status.output.strip() == 'trigger_restart_framework':
decrypted = 'true'
#Wait for boot to complete. The boot completed intent is broadcast before
#boot is actually completed when encryption is enabled. So 'key' off the
#animation.
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
print 'wait for animation to start'
while status == 'stopped':
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
print 'waiting for animation to finish'
while status == 'running':
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
else:
boot = False
while(not boot):
self.adb.adb_wait_for_device(self.serial)
res = self.adb.adb_shell(self.serial, "getprop sys.boot_completed")
if len(res.output.strip()) != 0 and int(res.output.strip()) is 1:
boot = True

Set clipboard text via adb shell as of API level 11

Before API level 11, it was possible to set the content of the clipboard by using the service program on the adb shell:
service call SERVICE CODE [i32 INT | s16 STR] ...
Options:
i32: Write the integer INT into the send parcel.
s16: Write the UTF-16 string STR into the send parcel.
There were three integer codes to define the methods:
1 TRANSACTION_getClipboardText
2 TRANSACTION_setClipboardText
3 TRANSACTION_hasClipboardText
For instance this command
$ adb shell service call clipboard 2 i32 1 i32 1 s16 "Hello Android!"
set the clipboard's content to "Hello Android!". As of API level 11 the listed methods are deprecated and the new ones take ClipData as an argument. How do you set the clipboard content now via adb shell?
You've asked two different questions here. The service calls are not related to the API functions.
Android is in general overly-aggressive about marking APIs as deprecated. In this case, it only means that there are new functions with more functionality. The functionality of getText(), hasText(), and setText() still exists and those functions will continue to work, but they are now implemented as trivial wrappers around the newer functions.
As far as the service calls go, those are an internal implementation detail and as you've noticed are not guaranteed to work across Android versions. If you peer into the Android source code, you'll find these transactions currently defined:
TRANSACTION_setPrimaryClip = 1
TRANSACTION_getPrimaryClip = 2
TRANSACTION_getPrimaryClipDescription = 3
TRANSACTION_hasPrimaryClip = 4
TRANSACTION_addPrimaryClipChangedListener = 5
TRANSACTION_removePrimaryClipChangedListener = 6
TRANSACTION_hasClipboardText = 7
The source code also indicates what parameters these transactions require. Unfortunately, TRANSACTION_setPrimaryClip requires a ClipData, which is not an i32 or an s16 and thus is not compatible with service call. We have bigger problems than that however; these transactions require the calling package name as a parameter, and the clipboard service validates that the specified package name matches the calling uid. When using the adb shell, the calling uid is either UID_ROOT or UID_SHELL, neither of which owns any packages, so there is no way to pass that check. Simply put, the new clipboard service cannot be used this way.
What can you do about all this? You can create your own service for manipulating the clipboard from the commandline and install it onto your device. I don't know if there's any way to extend service call, but you can use am startservice as a suitable replacement. If you've created and installed that custom clipboard service, then you could invoke it as:
am startservice -a MySetClipboard -e text "clipboard text"
The code to implement this service could look like this:
public MyService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
String text = intent.getStringExtra("text");
ClipboardManager.setText(text);
stopSelf();
return START_NOT_STICKY;
}
}
The service should have an intent-filter that declares it capable of handling the MySetClipboard intent action.
I found a way to do this using com.tengu.sharetoclipboard. You install it with F-Droid, then you start it with am over adb with the following arguments:
adb shell am start-activity \
-a android.intent.action.SEND \
-e android.intent.extra.TEXT <sh-escaped-text> \
-t 'text/plain' \
com.tengu.sharetoclipboard
<sh-escaped-text> is the new contents of the android clipboard. Note that this text must be escaped so that it is not interpreted specially by sh on the remote end. In practice, that means surrounding it with single quotes and replacing all single quotes with '\''. For instance, this would work fine if the local shell is fish:
adb shell am start-activity \
-a android.intent.action.SEND \
-e android.intent.extra.TEXT '\'I\'\\\'\'m pretty sure $HOME is set.\'' \
-t 'text/plain' \
com.tengu.sharetoclipboard
After fish parses it, the argument is 'I'\''m pretty sure $HOME is set.'. After sh parses it, the argument is I'm pretty sure $HOME is set..
Here's a python script to simplify this process:
#!/usr/bin/env python
import sys
import os
def shsafe(s):
return "'" + s.replace("'", "'\\''") + "'"
def exec_adb(text):
os.execvp('adb', [
'adb', 'shell', 'am', 'start-activity',
'-a', 'android.intent.action.SEND',
'-e', 'android.intent.extra.TEXT', shsafe(text),
'-t', 'text/plain',
'com.tengu.sharetoclipboard',
])
if sys.stdin.isatty():
if len(sys.argv) >= 2:
exec_adb(' '.join(sys.argv[1:]))
else:
sys.stderr.write(
'''Send something to the android clipboard over ADB. Requires
com.tengu.sharetoclipboard.
acb <text>
<some command> | acb
acb <some_text_file.txt''')
exit(1)
else:
exec_adb(sys.stdin.read())

Accessing LogCat from Android via Python

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.

Categories

Resources