1818@author: Diego Torres Milano
1919'''
2020
21- __version__ = '3.2 .0'
21+ __version__ = '4.0 .0'
2222
2323import sys
2424import subprocess
2525import re
2626import socket
2727import os
28- import java
2928import types
3029import time
3130import signal
3231import warnings
3332import copy
3433import pickle
34+ import platform
3535import xml .parsers .expat
36- import org .python .modules .sre .PatternObject
37- from com .android .monkeyrunner import MonkeyDevice , MonkeyRunner
36+ from com .dtmilano .android .adb import adbclient
3837
3938DEBUG = False
4039DEBUG_DEVICE = DEBUG and False
6059''' This assumes the smallest touchable view on the screen is approximately 50px x 50px
6160 and touches it at M{(x+OFFSET, y+OFFSET)} '''
6261
63- USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES = True
64- ''' Use monkeyrunner (C{MonkeyDevice.getProperty()}) to obtain the needed properties. If this is
62+ USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES = True
63+ ''' Use C{AdbClient} to obtain the needed properties. If this is
6564 C{False} then C{adb shell getprop} is used '''
6665
6766SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED = False
6867''' Skips some classes related with the Action Bar and the PhoneWindow$DecorView in the
6968 coordinates calculation
7069 @see: L{View.getXY()} '''
7170
71+ VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED = False
72+ ''' Under some conditions the touch event should be longer [t(DOWN) << t(UP)]. C{True} enables a
73+ workaround to delay the events.'''
74+
7275# some device properties
73- VERSION_SDK_PROPERTY = 'version.sdk'
76+ VERSION_SDK_PROPERTY = 'ro.build.version.sdk'
77+ VERSION_RELEASE_PROPERTY = 'ro.build.version.release'
7478
7579# some constants for the attributes
7680ID_PROPERTY = 'mID'
9599INVISIBLE = 0x4
96100GONE = 0x8
97101
102+ RegexType = type (re .compile ('' ))
98103IP_RE = re .compile ('^(\d{1,3}\.){3}\d{1,3}$' )
99104ID_RE = re .compile ('id/([^/]*)(/(\d+))?' )
100105
101- def __nd (name ):
106+ def _nd (name ):
102107 '''
103108 @return: Returns a named decimal regex
104109 '''
105110 return '(?P<%s>\d+)' % name
106111
107- def __nh (name ):
112+ def _nh (name ):
108113 '''
109114 @return: Returns a named hex regex
110115 '''
111116 return '(?P<%s>[0-9a-f]+)' % name
112117
113- def __ns (name , greedy = False ):
118+ def _ns (name , greedy = False ):
114119 '''
115120 NOTICE: this is using a non-greedy (or minimal) regex
116121
@@ -179,7 +184,7 @@ class ViewNotFoundException(Exception):
179184 '''
180185
181186 def __init__ (self , attr , value , root ):
182- if isinstance (value , org . python . modules . sre . PatternObject ):
187+ if isinstance (value , RegexType ):
183188 msg = "Couldn't find View with %s that matches '%s' in tree with root=%s" % (attr , value .pattern , root )
184189 else :
185190 msg = "Couldn't find View with %s='%s' in tree with root=%s" % (attr , value , root )
@@ -242,8 +247,8 @@ def __init__(self, map, device, version=-1, forceviewserveruse=False):
242247 self .build [VERSION_SDK_PROPERTY ] = version
243248 else :
244249 try :
245- if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES :
246- self .build [VERSION_SDK_PROPERTY ] = int (device .getProperty ('build.' + VERSION_SDK_PROPERTY ))
250+ if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES :
251+ self .build [VERSION_SDK_PROPERTY ] = int (device .getProperty (VERSION_SDK_PROPERTY ))
247252 else :
248253 self .build [VERSION_SDK_PROPERTY ] = int (device .shell ('getprop ro.build.' + VERSION_SDK_PROPERTY )[:- 2 ])
249254 except :
@@ -689,20 +694,20 @@ def __dumpWindowsInformation(self, debug=False):
689694 if DEBUG_WINDOWS or debug : print >> sys .stderr , dww
690695 lines = dww .split ('\n ' )
691696 widRE = re .compile ('^ *Window #%s Window{%s (u\d+ )?%s?.*}:' %
692- (__nd ('num' ), __nh ('winId' ), __ns ('activity' , greedy = True )))
693- currentFocusRE = re .compile ('^ mCurrentFocus=Window{%s .*' % __nh ('winId' ))
694- viewVisibilityRE = re .compile (' mViewVisibility=0x%s ' % __nh ('visibility' ))
697+ (_nd ('num' ), _nh ('winId' ), _ns ('activity' , greedy = True )))
698+ currentFocusRE = re .compile ('^ mCurrentFocus=Window{%s .*' % _nh ('winId' ))
699+ viewVisibilityRE = re .compile (' mViewVisibility=0x%s ' % _nh ('visibility' ))
695700 # This is for 4.0.4 API-15
696701 containingFrameRE = re .compile ('^ *mContainingFrame=\[%s,%s\]\[%s,%s\] mParentFrame=\[%s,%s\]\[%s,%s\]' %
697- (__nd ('cx' ), __nd ('cy' ), __nd ('cw' ), __nd ('ch' ), __nd ('px' ), __nd ('py' ), __nd ('pw' ), __nd ('ph' )))
702+ (_nd ('cx' ), _nd ('cy' ), _nd ('cw' ), _nd ('ch' ), _nd ('px' ), _nd ('py' ), _nd ('pw' ), _nd ('ph' )))
698703 contentFrameRE = re .compile ('^ *mContentFrame=\[%s,%s\]\[%s,%s\] mVisibleFrame=\[%s,%s\]\[%s,%s\]' %
699- (__nd ('x' ), __nd ('y' ), __nd ('w' ), __nd ('h' ), __nd ('vx' ), __nd ('vy' ), __nd ('vx1' ), __nd ('vy1' )))
704+ (_nd ('x' ), _nd ('y' ), _nd ('w' ), _nd ('h' ), _nd ('vx' ), _nd ('vy' ), _nd ('vx1' ), _nd ('vy1' )))
700705 # This is for 4.1 API-16
701706 framesRE = re .compile ('^ *Frames: containing=\[%s,%s\]\[%s,%s\] parent=\[%s,%s\]\[%s,%s\]' %
702- (__nd ('cx' ), __nd ('cy' ), __nd ('cw' ), __nd ('ch' ), __nd ('px' ), __nd ('py' ), __nd ('pw' ), __nd ('ph' )))
707+ (_nd ('cx' ), _nd ('cy' ), _nd ('cw' ), _nd ('ch' ), _nd ('px' ), _nd ('py' ), _nd ('pw' ), _nd ('ph' )))
703708 contentRE = re .compile ('^ *content=\[%s,%s\]\[%s,%s\] visible=\[%s,%s\]\[%s,%s\]' %
704- (__nd ('x' ), __nd ('y' ), __nd ('w' ), __nd ('h' ), __nd ('vx' ), __nd ('vy' ), __nd ('vx1' ), __nd ('vy1' )))
705- policyVisibilityRE = re .compile ('mPolicyVisibility=%s ' % __ns ('policyVisibility' , greedy = True ))
709+ (_nd ('x' ), _nd ('y' ), _nd ('w' ), _nd ('h' ), _nd ('vx' ), _nd ('vy' ), _nd ('vx1' ), _nd ('vy1' )))
710+ policyVisibilityRE = re .compile ('mPolicyVisibility=%s ' % _ns ('policyVisibility' , greedy = True ))
706711
707712 for l in range (len (lines )):
708713 m = widRE .search (lines [l ])
@@ -783,20 +788,20 @@ def __dumpWindowsInformation(self, debug=False):
783788 if DEBUG_COORDS : print >> sys .stderr , "__dumpWindowsInformation: (0,0)"
784789 return (0 ,0 )
785790
786- def touch (self , type = MonkeyDevice .DOWN_AND_UP ):
791+ def touch (self , type = adbclient .DOWN_AND_UP ):
787792 '''
788793 Touches the center of this C{View}
789794 '''
790795
791796 (x , y ) = self .getCenter ()
792797 if DEBUG_TOUCH :
793798 print >> sys .stderr , "should touch @ (%d, %d)" % (x , y )
794- if type == MonkeyDevice .DOWN_AND_UP :
799+ if VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED and type == adbclient .DOWN_AND_UP :
795800 if WARNINGS :
796801 print >> sys .stderr , "ViewClient: touch workaround enabled"
797- self .device .touch (x , y , MonkeyDevice .DOWN )
802+ self .device .touch (x , y , adbclient .DOWN )
798803 time .sleep (50 / 1000.0 )
799- self .device .touch (x + 10 , y + 10 , MonkeyDevice .UP )
804+ self .device .touch (x + 10 , y + 10 , adbclient .UP )
800805 else :
801806 self .device .touch (x , y , type )
802807
@@ -897,7 +902,12 @@ def __str__(self):
897902 if "class" in self .map :
898903 __str += " class=" + self .map ["class" ].__str__ () + " "
899904 for a in self .map :
900- __str += a + "=" + unicode (self .map [a ], 'utf-8' , 'replace' ) + " "
905+ __str += a + "="
906+ if isinstance (self .map [a ], unicode ):
907+ __str += self .map [a ].encode ('utf-8' , 'replace' )
908+ else :
909+ __str += unicode (str (self .map [a ]), 'utf-8' , 'replace' )
910+ __str += " "
901911 __str += "] parent="
902912 if self .parent :
903913 if "class" in self .parent .map :
@@ -923,18 +933,18 @@ class EditText(TextView):
923933
924934 def type (self , text ):
925935 self .touch ()
926- MonkeyRunner .sleep (1 )
936+ time .sleep (1 )
927937 for c in text :
928938 if c != ' ' :
929939 self .device .type (c )
930940 else :
931- self .device .press ('KEYCODE_SPACE' , MonkeyDevice .DOWN_AND_UP )
932- MonkeyRunner .sleep (1 )
941+ self .device .press ('KEYCODE_SPACE' , adbclient .DOWN_AND_UP )
942+ time .sleep (1 )
933943
934944 def backspace (self ):
935945 self .touch ()
936- MonkeyRunner .sleep (1 )
937- self .device .press ('KEYCODE_DEL' , MonkeyDevice .DOWN_AND_UP )
946+ time .sleep (1 )
947+ self .device .press ('KEYCODE_DEL' , adbclient .DOWN_AND_UP )
938948
939949class UiAutomator2AndroidViewClient ():
940950 '''
@@ -1003,7 +1013,7 @@ def Parse(self, uiautomatorxml):
10031013 parser .CharacterDataHandler = self .CharacterData
10041014 # Parse the XML File
10051015 try :
1006- parserStatus = parser .Parse (uiautomatorxml , 1 )
1016+ parserStatus = parser .Parse (uiautomatorxml . encode ( encoding = 'utf-8' , errors = 'replace' ), True )
10071017 except xml .parsers .expat .ExpatError , ex :
10081018 print >> sys .stderr , "ERROR: Offending XML:\n " , repr (uiautomatorxml )
10091019 raise RuntimeError (ex )
@@ -1111,7 +1121,10 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
11111121 if not os .access (adb , os .X_OK ):
11121122 raise Exception ('adb="%s" is not executable' % adb )
11131123 else :
1114- adb = ViewClient .__obtainAdbPath ()
1124+ # Using adbclient we don't need adb executable yet (maybe it's needed if we want to
1125+ # start adb if not running)
1126+ #adb = ViewClient.__obtainAdbPath()
1127+ adb = 'ADBCLIENT'
11151128
11161129 self .adb = adb
11171130 ''' The adb command '''
@@ -1123,7 +1136,7 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
11231136 ''' The map containing the device's display properties: width, height and density '''
11241137 for prop in [ 'width' , 'height' , 'density' ]:
11251138 self .display [prop ] = - 1
1126- if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES :
1139+ if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES :
11271140 try :
11281141 self .display [prop ] = int (device .getProperty ('display.' + prop ))
11291142 except :
@@ -1136,11 +1149,12 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
11361149
11371150 self .build = {}
11381151 ''' The map containing the device's build properties: version.sdk, version.release '''
1139- for prop in [VERSION_SDK_PROPERTY , 'version.release' ]:
1152+
1153+ for prop in [VERSION_SDK_PROPERTY , VERSION_RELEASE_PROPERTY ]:
11401154 self .build [prop ] = - 1
11411155 try :
1142- if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES :
1143- self .build [prop ] = device .getProperty ('build.' + prop )
1156+ if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES :
1157+ self .build [prop ] = device .getProperty (prop )
11441158 else :
11451159 self .build [prop ] = device .shell ('getprop ro.build.' + prop )[:- 2 ]
11461160 except :
@@ -1164,14 +1178,16 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
11641178 self .forceViewServerUse = forceviewserveruse
11651179 ''' Force the use of ViewServer even if the conditions to use UiAutomator are satisfied '''
11661180 self .useUiAutomator = (self .build [VERSION_SDK_PROPERTY ] >= 16 ) and not forceviewserveruse # jelly bean 4.1 & 4.2
1181+ if DEBUG :
1182+ print >> sys .stderr , " ViewClient.__init__: useUiAutomator=" , self .useUiAutomator , "sdk=" , self .build [VERSION_SDK_PROPERTY ], "forceviewserveruse=" , forceviewserveruse
11671183 ''' If UIAutomator is supported by the device it will be used '''
11681184 self .ignoreUiAutomatorKilled = ignoreuiautomatorkilled
11691185 ''' On some devices (i.e. Nexus 7 running 4.2.2) uiautomator is killed just after generating
11701186 the dump file. In many cases the file is already complete so we can ask to ignore the 'Killed'
11711187 message by setting L{ignoreuiautomatorkilled} to C{True}.
11721188
1173- Changes in 2 .3.21 that uses C{/dev/tty} instead of a file may have turned this variable
1174- unnnecessary , however it has been kept for backward compatibility.
1189+ Changes in v2 .3.21 that uses C{/dev/tty} instead of a file may have turned this variable
1190+ unnecessary , however it has been kept for backward compatibility.
11751191 '''
11761192
11771193 if self .useUiAutomator :
@@ -1218,7 +1234,7 @@ def __obtainAdbPath():
12181234 Obtains the ADB path attempting know locations for different OSs
12191235 '''
12201236
1221- osName = java . lang . System . getProperty ( 'os.name' )
1237+ osName = platform . system ( )
12221238 isWindows = False
12231239 if osName .startswith ('Windows' ):
12241240 adb = 'adb.exe'
@@ -1312,7 +1328,7 @@ def __obtainDeviceSerialNumber(device):
13121328
13131329 @staticmethod
13141330 def setAlarm (timeout ):
1315- osName = java . lang . System . getProperty ( 'os.name' )
1331+ osName = platform . system ( )
13161332 if osName .startswith ('Windows' ): # alarm is not implemented in Windows
13171333 return
13181334 signal .alarm (timeout )
@@ -1355,21 +1371,14 @@ def connectToDeviceOrExit(timeout=60, verbose=False, ignoresecuredevice=False, s
13551371 if verbose :
13561372 print >> sys .stderr , 'Connecting to a device with serialno=%s with a timeout of %d secs...' % \
13571373 (serialno , timeout )
1358- # Sometimes MonkeyRunner doesn't even timeout (i.e. two connections from same process), so let's
1359- # handle it here
13601374 ViewClient .setAlarm (timeout + 5 )
1361- device = MonkeyRunner . waitForConnection ( timeout , serialno )
1375+ device = adbclient . AdbClient ( serialno )
13621376 ViewClient .setAlarm (0 )
1363- try :
1364- device .wake ()
1365- except java .lang .NullPointerException , e :
1366- print >> sys .stderr , "%s: ERROR: Couldn't connect to %s: %s" % (progname , serialno , e )
1367- sys .exit (3 )
13681377 if verbose :
13691378 print >> sys .stderr , 'Connected to device with serialno=%s' % serialno
13701379 secure = device .getSystemProperty ('ro.secure' )
13711380 debuggable = device .getSystemProperty ('ro.debuggable' )
1372- versionProperty = device .getProperty ('build.' + VERSION_SDK_PROPERTY )
1381+ versionProperty = device .getProperty (VERSION_SDK_PROPERTY )
13731382 if versionProperty :
13741383 version = int (versionProperty )
13751384 else :
@@ -1575,7 +1584,7 @@ def __splitAttrs(self, strArgs):
15751584 raise RuntimeError ("This method is not compatible with UIAutomator" )
15761585 # replace the spaces in text:mText to preserve them in later split
15771586 # they are translated back after the attribute matches
1578- textRE = re .compile ('%s=%s,' % (self .textProperty , __nd ('len' )))
1587+ textRE = re .compile ('%s=%s,' % (self .textProperty , _nd ('len' )))
15791588 m = textRE .search (strArgs )
15801589 if m :
15811590 __textStart = m .end ()
@@ -1586,8 +1595,8 @@ def __splitAttrs(self, strArgs):
15861595 strArgs = strArgs .replace (s1 , s2 , 1 )
15871596
15881597 idRE = re .compile ("(?P<viewId>id/\S+)" )
1589- attrRE = re .compile ('%s(?P<parens>\(\))?=%s,(?P<val>[^ ]*)' % (__ns ('attr' ), __nd ('len' )), flags = re .DOTALL )
1590- hashRE = re .compile ('%s@%s' % (__ns ('class' ), __nh ('oid' )))
1598+ attrRE = re .compile ('%s(?P<parens>\(\))?=%s,(?P<val>[^ ]*)' % (_ns ('attr' ), _nd ('len' )), flags = re .DOTALL )
1599+ hashRE = re .compile ('%s@%s' % (_ns ('class' ), _nh ('oid' )))
15911600
15921601 attrs = {}
15931602 viewId = None
@@ -1783,15 +1792,14 @@ def dump(self, window=-1, sleep=1):
17831792 '''
17841793
17851794 if sleep > 0 :
1786- MonkeyRunner .sleep (sleep )
1795+ time .sleep (sleep )
17871796
17881797 if self .useUiAutomator :
17891798 # NOTICE:
17901799 # Using /dev/tty this works even on devices with no sdcard
1791- received = self .device .shell ('uiautomator dump /dev/tty >/dev/null' )
1800+ received = unicode ( self .device .shell ('uiautomator dump /dev/tty >/dev/null' ), encoding = 'utf-8' , errors = 'replace ' )
17921801 if not received :
17931802 raise RuntimeError ('ERROR: Empty UiAutomator dump was received' )
1794- received = received .encode ('utf-8' , 'ignore' )
17951803 if DEBUG :
17961804 self .received = received
17971805 if DEBUG_RECEIVED :
@@ -1900,7 +1908,7 @@ def list(self, sleep=1):
19001908 '''
19011909
19021910 if sleep > 0 :
1903- MonkeyRunner .sleep (sleep )
1911+ time .sleep (sleep )
19041912
19051913 if self .useUiAutomator :
19061914 raise Exception ("Not implemented yet: listing windows with UiAutomator" )
@@ -2039,7 +2047,7 @@ def __findViewWithAttributeInTree(self, attr, val, root):
20392047 if DEBUG : print >> sys .stderr , "__findViewWithAttributeInTree: type val=" , type (val )
20402048 if DEBUG : print >> sys .stderr , "__findViewWithAttributeInTree: checking if root=%s has attr=%s == %s" % (root .__smallStr__ (), attr , val )
20412049
2042- if isinstance (val , org . python . modules . sre . PatternObject ):
2050+ if isinstance (val , RegexType ):
20432051 return self .__findViewWithAttributeInTreeThatMatches (attr , val , root )
20442052 else :
20452053 if root and attr in root .map and root .map [attr ] == val :
@@ -2118,7 +2126,7 @@ def findViewWithAttributeThatMatches(self, attr, regex, root="ROOT"):
21182126 def findViewWithText (self , text , root = "ROOT" ):
21192127 if DEBUG :
21202128 print >> sys .stderr , "findViewWithText(%s, %s)" % (text , root )
2121- if isinstance (text , org . python . modules . sre . PatternObject ):
2129+ if isinstance (text , RegexType ):
21222130 return self .findViewWithAttributeThatMatches (self .textProperty , text , root )
21232131 #l = self.findViewWithAttributeThatMatches(TEXT_PROPERTY, text)
21242132 #ll = len(l)
0 commit comments