2929import time
3030import signal
3131import warnings
32+ import xml .parsers .expat
3233from com .android .monkeyrunner import MonkeyDevice , MonkeyRunner
3334
3435DEBUG = True
35- DEBUG_DEVICE = DEBUG and True
36- DEBUG_RECEIVED = DEBUG and True
36+ DEBUG_DEVICE = DEBUG and False
37+ DEBUG_RECEIVED = DEBUG and False
3738DEBUG_TREE = DEBUG and False
3839DEBUG_GETATTR = DEBUG and False
39- DEBUG_COORDS = DEBUG and False
40+ DEBUG_COORDS = DEBUG and True
4041DEBUG_TOUCH = DEBUG and False
4142DEBUG_STATUSBAR = DEBUG and False
4243DEBUG_WINDOWS = DEBUG and False
6465VERSION_SDK_PROPERTY = 'version.sdk'
6566
6667# some constants for the attributes
68+ ID_PROPERTY = 'mID'
6769TEXT_PROPERTY = 'text:mText'
6870TEXT_PROPERTY_API_10 = 'mText'
6971WS = "\xfe " # the whitespace replacement char for TEXT_PROPERTY
@@ -284,7 +286,7 @@ def getId(self):
284286 '''
285287
286288 try :
287- return self .map ['mID' ]
289+ return self .map [ID_PROPERTY ]
288290 except :
289291 return None
290292
@@ -304,16 +306,22 @@ def getText(self):
304306 return None
305307
306308 def getHeight (self ):
307- try :
308- return int (self .map ['layout:getHeight()' ])
309- except :
310- return 0
309+ if USE_UI_AUTOMATOR :
310+ return self .map ['bounds' ][1 ][1 ]
311+ else :
312+ try :
313+ return int (self .map ['layout:getHeight()' ])
314+ except :
315+ return 0
311316
312317 def getWidth (self ):
313- try :
314- return int (self .map ['layout:getWidth()' ])
315- except :
316- return 0
318+ if USE_UI_AUTOMATOR :
319+ return self .map ['bounds' ][1 ][0 ]
320+ else :
321+ try :
322+ return int (self .map ['layout:getWidth()' ])
323+ except :
324+ return 0
317325
318326 def getUniqueId (self ):
319327 '''
@@ -352,12 +360,16 @@ def getX(self):
352360 if DEBUG_COORDS :
353361 print >> sys .stderr , "getX(%s %s ## %s)" % (self .getClass (), self .getId (), self .getUniqueId ())
354362 x = 0
355- try :
356- if GET_VISIBILITY_PROPERTY in self .map and self .map [GET_VISIBILITY_PROPERTY ] == 'VISIBLE' :
357- if DEBUG_COORDS : print >> sys .stderr , " getX: VISIBLE adding %d" % int (self .map ['layout:mLeft' ])
358- x += int (self .map ['layout:mLeft' ])
359- except :
360- warnings .warn ("View %s has no 'layout:mLeft' property" % self .getId ())
363+
364+ if USE_UI_AUTOMATOR :
365+ x = self .map ['bounds' ][0 ][0 ]
366+ else :
367+ try :
368+ if GET_VISIBILITY_PROPERTY in self .map and self .map [GET_VISIBILITY_PROPERTY ] == 'VISIBLE' :
369+ if DEBUG_COORDS : print >> sys .stderr , " getX: VISIBLE adding %d" % int (self .map ['layout:mLeft' ])
370+ x += int (self .map ['layout:mLeft' ])
371+ except :
372+ warnings .warn ("View %s has no 'layout:mLeft' property" % self .getId ())
361373
362374 if DEBUG_COORDS : print >> sys .stderr , " getX: returning %d" % (x )
363375 return x
@@ -370,12 +382,16 @@ def getY(self):
370382 if DEBUG_COORDS :
371383 print >> sys .stderr , "getY(%s %s ## %s)" % (self .getClass (), self .getId (), self .getUniqueId ())
372384 y = 0
373- try :
374- if GET_VISIBILITY_PROPERTY in self .map and self .map [GET_VISIBILITY_PROPERTY ] == 'VISIBLE' :
375- if DEBUG_COORDS : print >> sys .stderr , " getY: VISIBLE adding %d" % int (self .map ['layout:mTop' ])
376- y += int (self .map ['layout:mTop' ])
377- except :
378- warnings .warn ("View %s has no 'layout:mTop' property" % self .getId ())
385+
386+ if USE_UI_AUTOMATOR :
387+ y = self .map ['bounds' ][0 ][1 ]
388+ else :
389+ try :
390+ if GET_VISIBILITY_PROPERTY in self .map and self .map [GET_VISIBILITY_PROPERTY ] == 'VISIBLE' :
391+ if DEBUG_COORDS : print >> sys .stderr , " getY: VISIBLE adding %d" % int (self .map ['layout:mTop' ])
392+ y += int (self .map ['layout:mTop' ])
393+ except :
394+ warnings .warn ("View %s has no 'layout:mTop' property" % self .getId ())
379395
380396 if DEBUG_COORDS : print >> sys .stderr , " getY: returning %d" % (y )
381397 return y
@@ -557,7 +573,7 @@ def __dumpWindowsInformation(self):
557573 if m :
558574 visibility = int (m .group ('visibility' ))
559575 if DEBUG_COORDS : print >> sys .stderr , "__dumpWindowsInformation: visibility=" , visibility
560- if self .build [VERSION_SDK_PROPERTY ] = = 16 :
576+ if self .build [VERSION_SDK_PROPERTY ] > = 16 :
561577 m = framesRE .search (lines [l2 ])
562578 if m :
563579 px , py = self .__obtainPxPy (m )
@@ -699,6 +715,73 @@ def type(self, text):
699715 self .device .type (text )
700716 MonkeyRunner .sleep (1 )
701717
718+ class UiAutomator2AndroidViewClient ():
719+ '''
720+ UiAutomator XML to AndroidViewClient
721+ '''
722+
723+ def __init__ (self , device , version ):
724+ self .device = device
725+ self .version = version
726+ self .root = None
727+ self .nodeStack = []
728+ self .parent = None
729+ self .viewsById = []
730+ self .idCount = 1
731+
732+ def StartElement (self , name , attributes ):
733+ '''
734+ Expat start element event handler
735+ '''
736+ if name == 'hierarchy' :
737+ pass
738+ elif name == 'node' :
739+ # Instantiate an Element object
740+ attributes ['uniqueId' ] = 'id/no_id/%d' % self .idCount
741+ bounds = re .split ('[\][,]' , attributes ['bounds' ])
742+ attributes ['bounds' ] = ((int (bounds [1 ]), int (bounds [2 ])), (int (bounds [4 ]), int (bounds [5 ])))
743+ self .idCount += 1
744+ child = View .factory (attributes , self .device , self .version )
745+ self .viewsById .append (child )
746+ # Push element onto the stack and make it a child of parent
747+ if not self .nodeStack :
748+ self .root = child
749+ else :
750+ self .parent = self .nodeStack [- 1 ]
751+ self .parent .add (child )
752+ self .nodeStack .append (child )
753+
754+ def EndElement (self , name ):
755+ '''
756+ Expat end element event handler
757+ '''
758+
759+ if name == 'hierarchy' :
760+ pass
761+ elif name == 'node' :
762+ self .nodeStack .pop ( )
763+
764+ def CharacterData (self , data ):
765+ '''
766+ Expat character data event handler
767+ '''
768+
769+ if data .strip ( ):
770+ data = data .encode ( )
771+ element = self .nodeStack [- 1 ]
772+ element .cdata += data
773+
774+ def Parse (self , uiautomatorxml ):
775+ # Create an Expat parser
776+ parser = xml .parsers .expat .ParserCreate ()
777+ # Set the Expat event handlers to our methods
778+ parser .StartElementHandler = self .StartElement
779+ parser .EndElementHandler = self .EndElement
780+ parser .CharacterDataHandler = self .CharacterData
781+ # Parse the XML File
782+ parserStatus = parser .Parse (uiautomatorxml , 1 )
783+ return self .root
784+
702785class ViewClient :
703786 '''
704787 ViewClient is a I{ViewServer} client.
@@ -784,7 +867,11 @@ def __init__(self, device, serialno, adb=None, autodump=True, localport=VIEW_SER
784867 TEXT_PROPERTY = TEXT_PROPERTY_API_10
785868 elif self .build [VERSION_SDK_PROPERTY ] > 16 : # jelly bean 4.2
786869 global USE_UI_AUTOMATOR
870+ global TEXT_PROPERTY
871+ global ID_PROPERTY
787872 USE_UI_AUTOMATOR = True
873+ TEXT_PROPERTY = 'text'
874+ ID_PROPERTY = 'uniqueId'
788875
789876 if not USE_UI_AUTOMATOR :
790877 if startviewserver :
@@ -1051,9 +1138,9 @@ def setViews(self, received):
10511138 raise ValueError ("received is empty" )
10521139 self .views = []
10531140 ''' The list of Views represented as C{str} obtained after splitting it into lines after being received from the server. Done by L{self.setViews()}. '''
1141+ self .__parseTree (received .split ("\n " ))
10541142 if DEBUG :
10551143 print >> sys .stderr , "there are %d views in this dump" % len (self .views )
1056- self .__parseTree (received .split ("\n " ))
10571144
10581145 def setViewsFromUiAutomatorDump (self , received ):
10591146 '''
@@ -1067,9 +1154,9 @@ def setViewsFromUiAutomatorDump(self, received):
10671154 raise ValueError ("received is empty" )
10681155 self .views = []
10691156 ''' The list of Views represented as C{str} obtained after splitting it into lines after being received from the server. Done by L{self.setViews()}. '''
1157+ self .__parseTreeFromUiAutomatorDump (received )
10701158 if DEBUG :
10711159 print >> sys .stderr , "there are %d views in this dump" % len (self .views )
1072- self .__parseTreeFromUiAutomatorDump (self .received )
10731160
10741161
10751162 def __splitAttrs (self , strArgs , addViewToViewsById = False ):
@@ -1218,7 +1305,9 @@ def __parseTree(self, receivedLines):
12181305 lastView = child
12191306
12201307 def __parseTreeFromUiAutomatorDump (self , receivedXml ):
1221- raise RuntimeError ("NOT IMPLEMENTED YET!" )
1308+ parser = UiAutomator2AndroidViewClient (self .device , self .build [VERSION_SDK_PROPERTY ])
1309+ self .root = parser .Parse (receivedXml )
1310+ self .views = parser .viewsById
12221311
12231312 def getRoot (self ):
12241313 '''
@@ -1277,14 +1366,16 @@ def dump(self, windowId=-1, sleep=1):
12771366 if not re .search ('dumped' , self .device .shell ('uiautomator dump /mnt/sdcard/window_dump.xml' )):
12781367 raise RuntimeError ('ERROR: Getting UIAutomator dump' )
12791368 received = self .device .shell ('cat /mnt/sdcard/window_dump.xml' )
1369+ if received :
1370+ received = received .encode ('ascii' , 'ignore' )
12801371 if DEBUG :
1281- self .received = received . encode ( 'ascii' , 'ignore' )
1372+ self .received = received
12821373 if DEBUG_RECEIVED :
12831374 print >> sys .stderr , "received %d chars" % len (received )
12841375 print >> sys .stderr
1285- print >> sys .stderr , self . received
1376+ print >> sys .stderr , received
12861377 print >> sys .stderr
1287- self .setViewsFromUiAutomatorDump (self . received )
1378+ self .setViewsFromUiAutomatorDump (received )
12881379 else :
12891380 s = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
12901381 try :
0 commit comments