Package src :: Package com :: Package dtmilano :: Package android :: Module viewclient |
[hide private]
[frames] | no frames]

# Source Code for Module src.com.dtmilano.android.viewclient

       1  # -*- coding: utf-8 -*-
       2  '''
       3  Copyright (C) 2012-2013  Diego Torres Milano
       4  Created on Feb 2, 2012
       5
       6  Licensed under the Apache License, Version 2.0 (the "License");
       7  you may not use this file except in compliance with the License.
       8  You may obtain a copy of the License at
       9
      10         http://www.apache.org/licenses/LICENSE-2.0
      11
      12  Unless required by applicable law or agreed to in writing, software
      13  distributed under the License is distributed on an "AS IS" BASIS,
      14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      15  See the License for the specific language governing permissions and
      16  limitations under the License.
      17
      18  @author: Diego Torres Milano
      19  '''
      20
      21  __version__ = '3.2.0'
      22
      23  import sys
      24  import subprocess
      25  import re
      26  import socket
      27  import os
      28  import java
      29  import types
      30  import time
      31  import signal
      32  import warnings
      33  import copy
      34  import pickle
      35  import xml.parsers.expat
      36  import org.python.modules.sre.PatternObject
      37  from com.android.monkeyrunner import MonkeyDevice, MonkeyRunner
      38
      39  DEBUG = False
      40  DEBUG_DEVICE = DEBUG and False
      41  DEBUG_RECEIVED = DEBUG and False
      42  DEBUG_TREE = DEBUG and False
      43  DEBUG_GETATTR = DEBUG and False
      44  DEBUG_CALL = DEBUG and False
      45  DEBUG_COORDS = DEBUG and False
      46  DEBUG_TOUCH = DEBUG and False
      47  DEBUG_STATUSBAR = DEBUG and False
      48  DEBUG_WINDOWS = DEBUG and False
      49  DEBUG_BOUNDS = DEBUG and False
      50  DEBUG_DISTANCE = DEBUG and False
      51
      52  WARNINGS = False
      53
      54  VIEW_SERVER_HOST = 'localhost'
      55  VIEW_SERVER_PORT = 4939
      56
      57  ADB_DEFAULT_PORT = 5555
      58
      59  OFFSET = 25
      60  ''' This assumes the smallest touchable view on the screen is approximately 50px x 50px
      61      and touches it at M{(x+OFFSET, y+OFFSET)} '''
      62
      63  USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES = True
      64  ''' Use monkeyrunner (C{MonkeyDevice.getProperty()}) to obtain the needed properties. If this is
      65      C{False} then C{adb shell getprop} is used '''
      66
      67  SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED = False
      68  ''' Skips some classes related with the Action Bar and the PhoneWindow$DecorView in the
      69      coordinates calculation
      70      @see: L{View.getXY()} '''
      71
      72  # some device properties
      73  VERSION_SDK_PROPERTY = 'version.sdk'
      74
      75  # some constants for the attributes
      76  ID_PROPERTY = 'mID'
      77  ID_PROPERTY_UI_AUTOMATOR = 'uniqueId'
      78  TEXT_PROPERTY = 'text:mText'
      79  TEXT_PROPERTY_API_10 = 'mText'
      80  TEXT_PROPERTY_UI_AUTOMATOR = 'text'
      81  WS = "\xfe" # the whitespace replacement char for TEXT_PROPERTY
      82  LEFT_PROPERTY = 'layout:mLeft'
      83  LEFT_PROPERTY_API_8 = 'mLeft'
      84  TOP_PROPERTY = 'layout:mTop'
      85  TOP_PROPERTY_API_8 = 'mTop'
      86  WIDTH_PROPERTY = 'layout:getWidth()'
      87  WIDTH_PROPERTY_API_8 = 'getWidth()'
      88  HEIGHT_PROPERTY = 'layout:getHeight()'
      89  HEIGHT_PROPERTY_API_8 = 'getHeight()'
      90  GET_VISIBILITY_PROPERTY = 'getVisibility()'
      91  LAYOUT_TOP_MARGIN_PROPERTY = 'layout:layout_topMargin'
      92
      93  # visibility
      94  VISIBLE = 0x0
      95  INVISIBLE = 0x4
      96  GONE = 0x8
      97
      98  IP_RE = re.compile('^(\d{1,3}\.){3}\d{1,3}$')
      99  ID_RE = re.compile('id/([^/]*)(/(\d+))?')
     100
     101 -def __nd(name):
     102      '''
     103      @return: Returns a named decimal regex
     104      '''
     105      return '(?P<%s>\d+)' % name
     106
     107 -def __nh(name):
     108      '''
     109      @return: Returns a named hex regex
     110      '''
     111      return '(?P<%s>[0-9a-f]+)' % name
     112
     113 -def __ns(name, greedy=False):
     114      '''
     115      NOTICE: this is using a non-greedy (or minimal) regex
     116
     117      @type name: str
     118      @param name: the name used to tag the expression
     119      @type greedy: bool
     120      @param greedy: Whether the regex is greedy or not
     121
     122      @return: Returns a named string regex (only non-whitespace characters allowed)
     123      '''
     124      return '(?P<%s>\S+%s)' % (name, '' if greedy else '?')
     125
     126
     127 -class Window:
     128      '''
     129      Window class
     130      '''
     131
     132 -    def __init__(self, num, winId, activity, wvx, wvy, wvw, wvh, px, py, visibility):
     133          '''
     134          Constructor
     135
     136          @type num: int
     137          @param num: Ordering number in Window Manager
     138          @type winId: str
     139          @param winId: the window ID
     140          @type activity: str
     141          @param activity: the activity (or sometimes other component) owning the window
     142          @type wvx: int
     143          @param wvx: window's virtual X
     144          @type wvy: int
     145          @param wvy: window's virtual Y
     146          @type wvw: int
     147          @param wvw: window's virtual width
     148          @type wvh: int
     149          @param wvh: window's virtual height
     150          @type px: int
     151          @param px: parent's X
     152          @type py: int
     153          @param py: parent's Y
     154          @type visibility: int
     155          @param visibility: visibility of the window
     156          '''
     157
     158          if DEBUG_COORDS: print >> sys.stderr, "Window(%d, %s, %s, %d, %d, %d, %d, %d, %d, %d)" % \
     159                  (num, winId, activity, wvx, wvy, wvw, wvh, px, py, visibility)
     160          self.num = num
     161          self.winId = winId
     162          self.activity = activity
     163          self.wvx = wvx
     164          self.wvy = wvy
     165          self.wvw = wvw
     166          self.wvh = wvh
     167          self.px = px
     168          self.py = py
     169          self.visibility = visibility
     170
     171 -    def __str__(self):
     172          return "Window(%d, wid=%s, a=%s, x=%d, y=%d, w=%d, h=%d, px=%d, py=%d, v=%d)" % \
     173                  (self.num, self.winId, self.activity, self.wvx, self.wvy, self.wvw, self.wvh, self.px, self.py, self.visibility)
     174
     175
     176 -class ViewNotFoundException(Exception):
     177      '''
     178      ViewNotFoundException is raised when a View is not found.
     179      '''
     180
     181 -    def __init__(self, attr, value, root):
     182          if isinstance(value, org.python.modules.sre.PatternObject):
     183              msg = "Couldn't find View with %s that matches '%s' in tree with root=%s" % (attr, value.pattern, root)
     184          else:
     185              msg = "Couldn't find View with %s='%s' in tree with root=%s" % (attr, value, root)
     186          super(Exception, self).__init__(msg)
     187
     188 -class View:
     189      '''
     190      View class
     191      '''
     192
     193      @staticmethod
     194 -    def factory(attrs, device, version=-1, forceviewserveruse=False):
     195          '''
     196          View factory
     197          '''
     198
     199          if attrs.has_key('class'):
     200              clazz = attrs['class']
     201              if clazz == 'android.widget.TextView':
     202                  return TextView(attrs, device, version, forceviewserveruse)
     203              elif clazz == 'android.widget.EditText':
     204                  return EditText(attrs, device, version, forceviewserveruse)
     205              else:
     206                  return View(attrs, device, version, forceviewserveruse)
     207          else:
     208              return View(attrs, device, version, forceviewserveruse)
     209
     210 -    def __init__(self, map, device, version=-1, forceviewserveruse=False):
     211          '''
     212          Constructor
     213
     214          @type map: map
     215          @param map: the map containing the (attribute, value) pairs
     216          @type device: MonkeyDevice
     217          @param device: the device containing this View
     218          @type version: int
     219          @param version: the Android SDK version number of the platform where this View belongs. If
     220                          this is C{-1} then the Android SDK version will be obtained in this
     221                          constructor.
     222          @type forceviewserveruse: boolean
     223          @param forceviewserveruse: Force the use of C{ViewServer} even if the conditions were given
     224                          to use C{UiAutomator}.
     225          '''
     226
     227          self.map = map
     228          ''' The map that contains the C{attr},C{value} pairs '''
     229          self.device = device
     230          ''' The MonkeyDevice '''
     231          self.children = []
     232          ''' The children of this View '''
     233          self.parent = None
     234          ''' The parent of this View '''
     235          self.windows = {}
     236          self.currentFocus = None
     237          ''' The current focus '''
     238          self.build = {}
     239          ''' Build properties '''
     240
     241          if version != -1:
     242              self.build[VERSION_SDK_PROPERTY] = version
     243          else:
     244              try:
     245                  if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES:
     246                      self.build[VERSION_SDK_PROPERTY] = int(device.getProperty('build.' + VERSION_SDK_PROPERTY))
     247                  else:
     248                      self.build[VERSION_SDK_PROPERTY] = int(device.shell('getprop ro.build.' + VERSION_SDK_PROPERTY)[:-2])
     249              except:
     250                  self.build[VERSION_SDK_PROPERTY] = -1
     251
     252          version = self.build[VERSION_SDK_PROPERTY]
     253          self.useUiAutomator = (version >= 16) and not forceviewserveruse
     254          ''' Whether to use UIAutomator or ViewServer '''
     255          self.idProperty = None
     256          ''' The id property depending on the View attribute format '''
     257          self.textProperty = None
     258          ''' The text property depending on the View attribute format '''
     259          self.leftProperty = None
     260          ''' The left property depending on the View attribute format '''
     261          self.topProperty = None
     262          ''' The top property depending on the View attribute format '''
     263          self.widthProperty = None
     264          ''' The width property depending on the View attribute format '''
     265          self.heightProperty = None
     266          ''' The height property depending on the View attribute format '''
     267          if version >= 16 and self.useUiAutomator:
     268              self.idProperty = ID_PROPERTY_UI_AUTOMATOR
     269              self.textProperty = TEXT_PROPERTY_UI_AUTOMATOR
     270              self.leftProperty = LEFT_PROPERTY
     271              self.topProperty = TOP_PROPERTY
     272              self.widthProperty = WIDTH_PROPERTY
     273              self.heightProperty = HEIGHT_PROPERTY
     274          elif version > 10 and (version < 16 or self.useUiAutomator):
     275              self.idProperty = ID_PROPERTY
     276              self.textProperty = TEXT_PROPERTY
     277              self.leftProperty = LEFT_PROPERTY
     278              self.topProperty = TOP_PROPERTY
     279              self.widthProperty = WIDTH_PROPERTY
     280              self.heightProperty = HEIGHT_PROPERTY
     281          elif version == 10:
     282              self.idProperty = ID_PROPERTY
     283              self.textProperty = TEXT_PROPERTY_API_10
     284              self.leftProperty = LEFT_PROPERTY
     285              self.topProperty = TOP_PROPERTY
     286              self.widthProperty = WIDTH_PROPERTY
     287              self.heightProperty = HEIGHT_PROPERTY
     288          elif version >= 7 and version < 10:
     289              self.idProperty = ID_PROPERTY
     290              self.textProperty = TEXT_PROPERTY_API_10
     291              self.leftProperty = LEFT_PROPERTY_API_8
     292              self.topProperty = TOP_PROPERTY_API_8
     293              self.widthProperty = WIDTH_PROPERTY_API_8
     294              self.heightProperty = HEIGHT_PROPERTY_API_8
     295          elif version > 0 and version < 7:
     296              self.idProperty = ID_PROPERTY
     297              self.textProperty = TEXT_PROPERTY_API_10
     298              self.leftProperty = LEFT_PROPERTY
     299              self.topProperty = TOP_PROPERTY
     300              self.widthProperty = WIDTH_PROPERTY
     301              self.heightProperty = HEIGHT_PROPERTY
     302          elif version == -1:
     303              self.idProperty = ID_PROPERTY
     304              self.textProperty = TEXT_PROPERTY
     305              self.leftProperty = LEFT_PROPERTY
     306              self.topProperty = TOP_PROPERTY
     307              self.widthProperty = WIDTH_PROPERTY
     308              self.heightProperty = HEIGHT_PROPERTY
     309          else:
     310              self.idProperty = ID_PROPERTY
     311              self.textProperty = TEXT_PROPERTY
     312              self.leftProperty = LEFT_PROPERTY
     313              self.topProperty = TOP_PROPERTY
     314              self.widthProperty = WIDTH_PROPERTY
     315              self.heightProperty = HEIGHT_PROPERTY
     316
     317 -    def __getitem__(self, key):
     318          return self.map[key]
     319
     320 -    def __getattr__(self, name):
     321          if DEBUG_GETATTR:
     322              print >>sys.stderr, "__getattr__(%s)    version: %d" % (name, self.build[VERSION_SDK_PROPERTY])
     323
     324          # NOTE:
     325          # I should try to see if 'name' is a defined method
     326          # but it seems that if I call locals() here an infinite loop is entered
     327
     328          if self.map.has_key(name):
     329              r = self.map[name]
     330          elif self.map.has_key(name + '()'):
     331              # the method names are stored in the map with their trailing '()'
     332              r = self.map[name + '()']
     333          elif name.count("_") > 0:
     334              mangledList = self.allPossibleNamesWithColon(name)
     335              mangledName = self.intersection(mangledList, self.map.keys())
     336              if len(mangledName) > 0 and self.map.has_key(mangledName[0]):
     337                  r = self.map[mangledName[0]]
     338              else:
     339                  # Default behavior
     340                  raise AttributeError, name
     341          else:
     342              # try removing 'is' prefix
     343              if DEBUG_GETATTR:
     344                  print >> sys.stderr, "    __getattr__: trying without 'is' prefix"
     345              suffix = name[2:].lower()
     346              if self.map.has_key(suffix):
     347                  r = self.map[suffix]
     348              else:
     349                  # Default behavior
     350                  raise AttributeError, name
     351
     352          # if the method name starts with 'is' let's assume its return value is boolean
     353  #         if name[:2] == 'is':
     354  #             r = True if r == 'true' else False
     355          if r == 'true':
     356              r = True
     357          elif r == 'false':
     358              r = False
     359
     360          # this should not cached in some way
     361          def innerMethod():
     362              if DEBUG_GETATTR:
     363                  print >>sys.stderr, "innerMethod: %s returning %s" % (innerMethod.__name__, r)
     364              return r
     365
     366          innerMethod.__name__ = name
     367
     368          # this should work, but then there's problems with the arguments of innerMethod
     369          # even if innerMethod(self) is added
     370          #setattr(View, innerMethod.__name__, innerMethod)
     371          #setattr(self, innerMethod.__name__, innerMethod)
     372
     373          return innerMethod
     374
     375 -    def __call__(self, *args, **kwargs):
     376          if DEBUG_CALL:
     377              print >>sys.stderr, "__call__(%s)" % (args if args else None)
     378
     379 -    def getClass(self):
     380          '''
     381          Gets the L{View} class
     382
     383          @return:  the L{View} class or C{None} if not defined
     384          '''
     385
     386          try:
     387              return self.map['class']
     388          except:
     389              return None
     390
     391 -    def getId(self):
     392          '''
     393          Gets the L{View} Id
     394
     395          @return: the L{View} C{Id} or C{None} if not defined
     396          @see: L{getUniqueId()}
     397          '''
     398
     399          try:
     400              return self.map[self.idProperty]
     401          except:
     402              return None
     403
     404 -    def getContentDescription(self):
     405          '''
     406          Gets the content description.
     407          '''
     408
     409          try:
     410              return self.map['content-desc']
     411          except:
     412              return None
     413
     414 -    def getParent(self):
     415          '''
     416          Gets the parent.
     417          '''
     418
     419          return self.parent
     420
     421 -    def getText(self):
     422          '''
     423          Gets the text attribute.
     424
     425          @return: the text attribute or C{None} if not defined
     426          '''
     427
     428          try:
     429              return self.map[self.textProperty]
     430          except Exception:
     431              return None
     432
     433 -    def getHeight(self):
     434          '''
     435          Gets the height.
     436          '''
     437
     438          if self.useUiAutomator:
     439              return self.map['bounds'][1][1] - self.map['bounds'][0][1]
     440          else:
     441              try:
     442                  return int(self.map[self.heightProperty])
     443              except:
     444                  return 0
     445
     446 -    def getWidth(self):
     447          '''
     448          Gets the width.
     449          '''
     450
     451          if self.useUiAutomator:
     452              return self.map['bounds'][1][0] - self.map['bounds'][0][0]
     453          else:
     454              try:
     455                  return int(self.map[self.widthProperty])
     456              except:
     457                  return 0
     458
     459 -    def getUniqueId(self):
     460          '''
     461          Gets the unique Id of this View.
     462
     463          @see: L{ViewClient.__splitAttrs()} for a discussion on B{Unique Ids}
     464          '''
     465
     466          try:
     467              return self.map['uniqueId']
     468          except:
     469              return None
     470
     471 -    def getVisibility(self):
     472          '''
     473          Gets the View visibility
     474          '''
     475
     476          try:
     477              if self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
     478                  return VISIBLE
     479              elif self.map[GET_VISIBILITY_PROPERTY] == 'INVISIBLE':
     480                  return INVISIBLE
     481              elif self.map[GET_VISIBILITY_PROPERTY] == 'GONE':
     482                  return GONE
     483              else:
     484                  return -2
     485          except:
     486              return -1
     487
     488 -    def getX(self):
     489          '''
     490          Gets the View X coordinate
     491          '''
     492
     493          if DEBUG_COORDS:
     494              print >>sys.stderr, "getX(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
     495          x = 0
     496
     497          if self.useUiAutomator:
     498              x = self.map['bounds'][0][0]
     499          else:
     500              try:
     501                  if GET_VISIBILITY_PROPERTY in self.map and self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
     502                      _x = int(self.map[self.leftProperty])
     503                      if DEBUG_COORDS: print >>sys.stderr, "   getX: VISIBLE adding %d" % _x
     504                      x += _x
     505              except:
     506                  warnings.warn("View %s has no '%s' property" % (self.getId(), self.leftProperty))
     507
     508          if DEBUG_COORDS: print >>sys.stderr, "   getX: returning %d" % (x)
     509          return x
     510
     511 -    def getY(self):
     512          '''
     513          Gets the View Y coordinate
     514          '''
     515
     516          if DEBUG_COORDS:
     517              print >>sys.stderr, "getY(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
     518          y = 0
     519
     520          if self.useUiAutomator:
     521              y = self.map['bounds'][0][1]
     522          else:
     523              try:
     524                  if GET_VISIBILITY_PROPERTY in self.map and self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
     525                      _y = int(self.map[self.topProperty])
     526                      if DEBUG_COORDS: print >>sys.stderr, "   getY: VISIBLE adding %d" % _y
     527                      y += _y
     528              except:
     529                  warnings.warn("View %s has no '%s' property" % (self.getId(), self.topProperty))
     530
     531          if DEBUG_COORDS: print >>sys.stderr, "   getY: returning %d" % (y)
     532          return y
     533
     534 -    def getXY(self, debug=False):
     535          '''
     536          Returns the I{screen} coordinates of this C{View}.
     537
     538          @return: The I{screen} coordinates of this C{View}
     539          '''
     540
     541          if DEBUG_COORDS or debug:
     542              try:
     543                  id = self.getId()
     544              except:
     545                  id = "NO_ID"
     546              print >> sys.stderr, "getXY(%s %s ## %s)" % (self.getClass(), id, self.getUniqueId())
     547
     548          x = self.getX()
     549          y = self.getY()
     550          parent = self.parent
     551          if DEBUG_COORDS: print >> sys.stderr, "   getXY: x=%s y=%s parent=%s" % (x, y, parent.getUniqueId() if parent else "None")
     552          hx = 0
     553          ''' Hierarchy accumulated X '''
     554          hy = 0
     555          ''' Hierarchy accumulated Y '''
     556
     557          if not self.useUiAutomator:
     558              if DEBUG_COORDS: print >> sys.stderr, "   getXY: not using UiAutomator, calculating ancestors coordinates"
     559              while parent != None:
     560                  if DEBUG_COORDS: print >> sys.stderr, "      getXY: parent: %s %s <<<<" % (parent.getClass() if parent else "None", parent.getUniqueId() if parent else "None")
     561                  if SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED:
     562                      if parent.getClass() in [ 'com.android.internal.widget.ActionBarView',
     563                                         'com.android.internal.widget.ActionBarContextView',
     564                                         'com.android.internal.view.menu.ActionMenuView',
     565                                         'com.android.internal.policy.impl.PhoneWindow$DecorView' ]:
     566                          if DEBUG_COORDS: print >> sys.stderr, "   getXY: skipping %s %s (%d,%d)" % (parent.getClass(), parent.getUniqueId(), parent.getX(), parent.getY())
     567                          parent = parent.parent
     568                          continue
     569                  if DEBUG_COORDS: print >> sys.stderr, "   getXY: parent=%s x=%d hx=%d y=%d hy=%d" % (parent.getUniqueId(), parent.getX(), hx, parent.getY(), hy)
     570                  hx += parent.getX()
     571                  hy += parent.getY()
     572                  parent = parent.parent
     573              if DEBUG_COORDS: print >> sys.stderr, "   getXY: parent=%s hx=%d hy=%d (end of loop)" % (parent.getUniqueId() if parent else "None", hx, hy)
     574
     575          (wvx, wvy) = self.__dumpWindowsInformation(debug=debug)
     576          if DEBUG_COORDS or debug:
     577              print >>sys.stderr, "   getXY: wv=(%d, %d) (windows information)" % (wvx, wvy)
     578          try:
     579              fw = self.windows[self.currentFocus]
     580              if DEBUG_STATUSBAR:
     581                  print >> sys.stderr, "    getXY: focused window=", fw
     582                  print >> sys.stderr, "    getXY: deciding whether to consider statusbar offset because current focused windows is at", (fw.wvx, fw.wvy), "parent", (fw.px, fw.py)
     583          except KeyError:
     584              fw = None
     585          (sbw, sbh) = self.__obtainStatusBarDimensionsIfVisible()
     586          if DEBUG_COORDS or debug:
     587              print >>sys.stderr, "   getXY: sb=(%d, %d) (statusbar dimensions)" % (sbw, sbh)
     588          statusBarOffset = 0
     589          pwx = 0
     590          pwy = 0
     591
     592          if fw:
     593              if DEBUG_COORDS:
     594                  print >>sys.stderr, "    getXY: focused window=", fw, "sb=", (sbw, sbh)
     595              if fw.wvy <= sbh: # it's very unlikely that fw.wvy < sbh, that is a window over the statusbar
     596                  if DEBUG_STATUSBAR: print >>sys.stderr, "        getXY: yes, considering offset=", sbh
     597                  statusBarOffset = sbh
     598              else:
     599                  if DEBUG_STATUSBAR: print >>sys.stderr, "        getXY: no, ignoring statusbar offset fw.wvy=", fw.wvy, ">", sbh
     600
     601              if fw.py == fw.wvy:
     602                  if DEBUG_STATUSBAR: print >>sys.stderr, "        getXY: but wait, fw.py == fw.wvy so we are adjusting by ", (fw.px, fw.py)
     603                  pwx = fw.px
     604                  pwy = fw.py
     605              else:
     606                  if DEBUG_STATUSBAR: print >>sys.stderr, "    getXY: fw.py=%d <= fw.wvy=%d, no adjustment" % (fw.py, fw.wvy)
     607
     608          if DEBUG_COORDS or DEBUG_STATUSBAR or debug:
     609              print >>sys.stderr, "   getXY: returning (%d, %d) ***" % (x+hx+wvx+pwx, y+hy+wvy-statusBarOffset+pwy)
     610              print >>sys.stderr, "                     x=%d+%d+%d+%d" % (x,hx,wvx,pwx)
     611              print >>sys.stderr, "                     y=%d+%d+%d-%d+%d" % (y,hy,wvy,statusBarOffset,pwy)
     612          return (x+hx+wvx+pwx, y+hy+wvy-statusBarOffset+pwy)
     613
     614 -    def getCoords(self):
     615          '''
     616          Gets the coords of the View
     617
     618          @return: A tuple containing the View's coordinates ((L, T), (R, B))
     619          '''
     620
     621          if DEBUG_COORDS:
     622              print >>sys.stderr, "getCoords(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
     623
     624          (x, y) = self.getXY();
     625          w = self.getWidth()
     626          h = self.getHeight()
     627          return ((x, y), (x+w, y+h))
     628
     629 -    def getPositionAndSize(self):
     630          '''
     631          Gets the position and size (X,Y, W, H)
     632
     633          @return: A tuple containing the View's coordinates (X, Y, W, H)
     634          '''
     635
     636          (x, y) = self.getXY();
     637          w = self.getWidth()
     638          h = self.getHeight()
     639          return (x, y, w, h)
     640
     641
     642 -    def getCenter(self):
     643          '''
     644          Gets the center coords of the View
     645
     646          @author: U{Dean Morin <https://github.com/deanmorin>}
     647          '''
     648
     649          (left, top), (right, bottom) = self.getCoords()
     650          x = left + (right - left) / 2
     651          y = top + (bottom - top) / 2
     652          return (x, y)
     653
     654 -    def __obtainStatusBarDimensionsIfVisible(self):
     655          sbw = 0
     656          sbh = 0
     657          for winId in self.windows:
     658              w = self.windows[winId]
     659              if DEBUG_COORDS: print >> sys.stderr, "      __obtainStatusBarDimensionsIfVisible: w=", w, "   w.activity=", w.activity, "%%%"
     660              if w.activity == 'StatusBar':
     661                  if w.wvy == 0 and w.visibility == 0:
     662                      if DEBUG_COORDS: print >> sys.stderr, "      __obtainStatusBarDimensionsIfVisible: statusBar=", (w.wvw, w.wvh)
     663                      sbw = w.wvw
     664                      sbh = w.wvh
     665                  break
     666
     667          return (sbw, sbh)
     668
     669 -    def __obtainVxVy(self, m):
     670          wvx = int(m.group('vx'))
     671          wvy = int(m.group('vy'))
     672          return wvx, wvy
     673
     674 -    def __obtainVwVh(self, m):
     675          (wvx, wvy) = self.__obtainVxVy(m)
     676          wvx1 = int(m.group('vx1'))
     677          wvy1 = int(m.group('vy1'))
     678          return (wvx1-wvx, wvy1-wvy)
     679
     680 -    def __obtainPxPy(self, m):
     681          px = int(m.group('px'))
     682          py = int(m.group('py'))
     683          return (px, py)
     684
     685 -    def __dumpWindowsInformation(self, debug=False):
     686          self.windows = {}
     687          self.currentFocus = None
     688          dww = self.device.shell('dumpsys window windows')
     689          if DEBUG_WINDOWS or debug: print >> sys.stderr, dww
     690          lines = dww.split('\n')
     691          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'))
     695          # This is for 4.0.4 API-15
     696          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')))
     698          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')))
     700          # This is for 4.1 API-16
     701          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')))
     703          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))
     706
     707          for l in range(len(lines)):
     708              m = widRE.search(lines[l])
     709              if m:
     710                  num = int(m.group('num'))
     711                  winId = m.group('winId')
     712                  activity = m.group('activity')
     713                  wvx = 0
     714                  wvy = 0
     715                  wvw = 0
     716                  wvh = 0
     717                  px = 0
     718                  py = 0
     719                  visibility = -1
     720                  policyVisibility = 0x0
     721
     722                  for l2 in range(l+1, len(lines)):
     723                      m = widRE.search(lines[l2])
     724                      if m:
     725                          l += (l2-1)
     726                          break
     727                      m = viewVisibilityRE.search(lines[l2])
     728                      if m:
     729                          visibility = int(m.group('visibility'))
     730                          if DEBUG_COORDS: print >> sys.stderr, "__dumpWindowsInformation: visibility=", visibility
     731                      if self.build[VERSION_SDK_PROPERTY] >= 17:
     732                          wvx, wvy = (0, 0)
     733                          wvw, wvh = (0, 0)
     734                      if self.build[VERSION_SDK_PROPERTY] >= 16:
     735                          m = framesRE.search(lines[l2])
     736                          if m:
     737                              px, py = self.__obtainPxPy(m)
     738                              m = contentRE.search(lines[l2+1])
     739                              if m:
     740                                  # FIXME: the information provided by 'dumpsys window windows' in 4.2.1 (API 16)
     741                                  # when there's a system dialog may not be correct and causes the View coordinates
     742                                  # be offset by this amount, see
     743                                  # https://github.com/dtmilano/AndroidViewClient/issues/29
     744                                  wvx, wvy = self.__obtainVxVy(m)
     745                                  wvw, wvh = self.__obtainVwVh(m)
     746                      elif self.build[VERSION_SDK_PROPERTY] == 15:
     747                          m = containingFrameRE.search(lines[l2])
     748                          if m:
     749                              px, py = self.__obtainPxPy(m)
     750                              m = contentFrameRE.search(lines[l2+1])
     751                              if m:
     752                                  wvx, wvy = self.__obtainVxVy(m)
     753                                  wvw, wvh = self.__obtainVwVh(m)
     754                      elif self.build[VERSION_SDK_PROPERTY] == 10:
     755                          m = containingFrameRE.search(lines[l2])
     756                          if m:
     757                              px, py = self.__obtainPxPy(m)
     758                              m = contentFrameRE.search(lines[l2+1])
     759                              if m:
     760                                  wvx, wvy = self.__obtainVxVy(m)
     761                                  wvw, wvh = self.__obtainVwVh(m)
     762                      else:
     763                          warnings.warn("Unsupported Android version %d" % self.build[VERSION_SDK_PROPERTY])
     764
     765                      #print >> sys.stderr, "Searching policyVisibility in", lines[l2]
     766                      m = policyVisibilityRE.search(lines[l2])
     767                      if m:
     768                          policyVisibility = 0x0 if m.group('policyVisibility') == 'true' else 0x8
     769
     770                  self.windows[winId] = Window(num, winId, activity, wvx, wvy, wvw, wvh, px, py, visibility + policyVisibility)
     771              else:
     772                  m = currentFocusRE.search(lines[l])
     773                  if m:
     774                      self.currentFocus = m.group('winId')
     775
     776          if self.currentFocus in self.windows and self.windows[self.currentFocus].visibility == 0:
     777              if DEBUG_COORDS or debug:
     778                  print >> sys.stderr, "__dumpWindowsInformation: focus=", self.currentFocus
     779                  print >> sys.stderr, "__dumpWindowsInformation:", self.windows[self.currentFocus]
     780              w = self.windows[self.currentFocus]
     781              return (w.wvx, w.wvy)
     782          else:
     783              if DEBUG_COORDS: print >> sys.stderr, "__dumpWindowsInformation: (0,0)"
     784              return (0,0)
     785
     786 -    def touch(self, type=MonkeyDevice.DOWN_AND_UP):
     787          '''
     788          Touches the center of this C{View}
     789          '''
     790
     791          (x, y) = self.getCenter()
     792          if DEBUG_TOUCH:
     793              print >>sys.stderr, "should touch @ (%d, %d)" % (x, y)
     794          if type == MonkeyDevice.DOWN_AND_UP:
     795              if WARNINGS:
     796                  print >> sys.stderr, "ViewClient: touch workaround enabled"
     797              self.device.touch(x, y, MonkeyDevice.DOWN)
     798              time.sleep(50/1000.0)
     799              self.device.touch(x+10, y+10, MonkeyDevice.UP)
     800          else:
     801              self.device.touch(x, y, type)
     802
     803 -    def allPossibleNamesWithColon(self, name):
     804          l = []
     805          for i in range(name.count("_")):
     806              name = name.replace("_", ":", 1)
     807              l.append(name)
     808          return l
     809
     810 -    def intersection(self, l1, l2):
     811          return list(set(l1) & set(l2))
     812
     813 -    def containsPoint(self, (x, y)):
     814          (X, Y, W, H) = self.getPositionAndSize()
     815          return (((x >= X) and (x <= (X+W)) and ((y >= Y) and (y <= (Y+H)))))
     816
     817 -    def add(self, child):
     818          '''
     819          Adds a child
     820
     821          @type child: View
     822          @param child: The child to add
     823          '''
     824          child.parent = self
     825          self.children.append(child)
     826
     827 -    def isClickable(self):
     828          return self.__getattr__('isClickable')()
     829
     830 -    def variableNameFromId(self):
     831          m = ID_RE.match(self.getUniqueId())
     832          if m:
     833              var = m.group(1)
     834              if m.group(3):
     835                  var += m.group(3)
     836              if re.match('^\d', var):
     837                  var = 'id_' + var
     838          return var
     839
     840 -    def writeImageToFile(self, filename, format="PNG"):
     841          '''
     842          Write the View image to the specified filename in the specified format.
     843
     844          @type filename: str
     845          @param filename: Absolute path and optional filename receiving the image. If this points to
     846                           a directory, then the filename is determined by this View unique ID and
     847                           format extension.
     848          @type format: str
     849          @param format: Image format (default format is PNG)
     850          '''
     851
     852          if not os.path.isabs(filename):
     853              raise ValueError("writeImageToFile expects an absolute path")
     854          if os.path.isdir(filename):
     855              filename = os.path.join(filename, self.variableNameFromId() + '.' + format.lower())
     856          if DEBUG:
     857              print >> sys.stderr, "writeImageToFile: saving image to '%s' in %s format" % (filename, format)
     858          self.device.takeSnapshot().getSubImage(self.getPositionAndSize()).writeToFile(filename, format)
     859
     860 -    def __smallStr__(self):
     861          __str = unicode("View[", 'utf-8', 'replace')
     862          if "class" in self.map:
     863              __str += " class=" + self.map['class']
     864          __str += " id=%s" % self.getId()
     865          __str += " ]   parent="
     866          if self.parent and "class" in self.parent.map:
     867              __str += "%s" % self.parent.map["class"]
     868          else:
     869              __str += "None"
     870
     871          return __str
     872
     873 -    def __tinyStr__(self):
     874          __str = unicode("View[", 'utf-8', 'replace')
     875          if "class" in self.map:
     876              __str += " class=" + re.sub('.*\.', '', self.map['class'])
     877          __str += " id=%s" % self.getId()
     878          __str += " ]"
     879
     880          return __str
     881
     882 -    def __microStr__(self):
     883          __str = unicode('', 'utf-8', 'replace')
     884          if "class" in self.map:
     885              __str += re.sub('.*\.', '', self.map['class'])
     886          id = self.getId().replace('id/no_id/', '-')
     887          __str += id
     888          ((L, T), (R, B)) = self.getCoords()
     889          __str += '@%04d%04d%04d%04d' % (L, T, R, B)
     890          __str += ''
     891
     892          return __str
     893
     894
     895 -    def __str__(self):
     896          __str = unicode("View[", 'utf-8', 'replace')
     897          if "class" in self.map:
     898              __str += " class=" + self.map["class"].__str__() + " "
     899          for a in self.map:
     900              __str += a + "=" + unicode(self.map[a], 'utf-8', 'replace') + " "
     901          __str += "]   parent="
     902          if self.parent:
     903              if "class" in self.parent.map:
     904                  __str += "%s" % self.parent.map["class"]
     905              else:
     906                  __str += self.parent.getId().__str__()
     907          else:
     908              __str += "None"
     909
     910          return __str
     911
     912 -class TextView(View):
     913      '''
     914      TextView class.
     915      '''
     916
     917      pass
     918
     919 -class EditText(TextView):
     920      '''
     921      EditText class.
     922      '''
     923
     924 -    def type(self, text):
     925          self.touch()
     926          MonkeyRunner.sleep(1)
     927          for c in text:
     928              if c != ' ':
     929                  self.device.type(c)
     930              else:
     931                  self.device.press('KEYCODE_SPACE', MonkeyDevice.DOWN_AND_UP)
     932          MonkeyRunner.sleep(1)
     933
     934 -    def backspace(self):
     935          self.touch()
     936          MonkeyRunner.sleep(1)
     937          self.device.press('KEYCODE_DEL', MonkeyDevice.DOWN_AND_UP)
     938
     939 -class UiAutomator2AndroidViewClient():
     940      '''
     941      UiAutomator XML to AndroidViewClient
     942      '''
     943
     944 -    def __init__(self, device, version):
     945          self.device = device
     946          self.version = version
     947          self.root = None
     948          self.nodeStack = []
     949          self.parent = None
     950          self.views = []
     951          self.idCount = 1
     952
     953 -    def StartElement(self, name, attributes):
     954          '''
     955          Expat start element event handler
     956          '''
     957          if name == 'hierarchy':
     958              pass
     959          elif name == 'node':
     960              # Instantiate an Element object
     961              attributes['uniqueId'] = 'id/no_id/%d' % self.idCount
     962              bounds = re.split('[\][,]', attributes['bounds'])
     963              attributes['bounds'] = ((int(bounds[1]), int(bounds[2])), (int(bounds[4]), int(bounds[5])))
     964              if DEBUG_BOUNDS:
     965                  print >> sys.stderr, "bounds=", attributes['bounds']
     966              self.idCount += 1
     967              child = View.factory(attributes, self.device, self.version)
     968              self.views.append(child)
     969              # Push element onto the stack and make it a child of parent
     970              if not self.nodeStack:
     971                  self.root = child
     972              else:
     973                  self.parent = self.nodeStack[-1]
     974                  self.parent.add(child)
     975              self.nodeStack.append(child)
     976
     977 -    def EndElement(self, name):
     978          '''
     979          Expat end element event handler
     980          '''
     981
     982          if name == 'hierarchy':
     983              pass
     984          elif name == 'node':
     985              self.nodeStack.pop()
     986
     987 -    def CharacterData(self, data):
     988          '''
     989          Expat character data event handler
     990          '''
     991
     992          if data.strip():
     993              data = data.encode()
     994              element = self.nodeStack[-1]
     995              element.cdata += data
     996
     997 -    def Parse(self, uiautomatorxml):
     998          # Create an Expat parser
     999          parser = xml.parsers.expat.ParserCreate()
    1000          # Set the Expat event handlers to our methods
    1001          parser.StartElementHandler = self.StartElement
    1002          parser.EndElementHandler = self.EndElement
    1003          parser.CharacterDataHandler = self.CharacterData
    1004          # Parse the XML File
    1005          try:
    1006              parserStatus = parser.Parse(uiautomatorxml, 1)
    1007          except xml.parsers.expat.ExpatError, ex:
    1008              print >>sys.stderr, "ERROR: Offending XML:\n", repr(uiautomatorxml)
    1009              raise RuntimeError(ex)
    1010          return self.root
    1011
    1012 -class Excerpt2Code():
    1013      ''' Excerpt XML to code '''
    1014
    1015 -    def __init__(self):
    1016          self.data = None
    1017
    1018 -    def StartElement(self, name, attributes):
    1019          '''
    1020          Expat start element event handler
    1021          '''
    1022          if name == 'excerpt':
    1023              pass
    1024          else:
    1025              warnings.warn("Unexpected element: '%s'" % name)
    1026
    1027 -    def EndElement(self, name):
    1028          '''
    1029          Expat end element event handler
    1030          '''
    1031
    1032          if name == 'excerpt':
    1033              pass
    1034
    1035 -    def CharacterData(self, data):
    1036          '''
    1037          Expat character data event handler
    1038          '''
    1039
    1040          if data.strip():
    1041              data = data.encode()
    1042              if not self.data:
    1043                  self.data = data
    1044              else:
    1045                  self.data += data
    1046
    1047 -    def Parse(self, excerpt):
    1048          # Create an Expat parser
    1049          parser = xml.parsers.expat.ParserCreate()
    1050          # Set the Expat event handlers to our methods
    1051          parser.StartElementHandler = self.StartElement
    1052          parser.EndElementHandler = self.EndElement
    1053          parser.CharacterDataHandler = self.CharacterData
    1054          # Parse the XML
    1055          parserStatus = parser.Parse(excerpt, 1)
    1056          return self.data
    1057
    1058 -class ViewClient:
    1059      '''
    1060      ViewClient is a I{ViewServer} client.
    1061
    1062      ViewServer backend
    1063      ==================
    1064      If not running the ViewServer is started on the target device or emulator and then the port
    1065      mapping is created.
    1066
    1067      UiAutomator backend
    1068      ===================
    1069      No service is started.
    1070      '''
    1071
    1072 -    def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse=False, localport=VIEW_SERVER_PORT, remoteport=VIEW_SERVER_PORT, startviewserver=True, ignoreuiautomatorkilled=False):
    1073          '''
    1074          Constructor
    1075
    1076          @type device: MonkeyDevice
    1077          @param device: The device running the C{View server} to which this client will connect
    1078          @type serialno: str
    1079          @param serialno: the serial number of the device or emulator to connect to
    1080          @type adb: str
    1081          @param adb: the path of the C{adb} executable or None and C{ViewClient} will try to find it
    1082          @type autodump: boolean
    1083          @param autodump: whether an automatic dump is performed at the end of this constructor
    1084          @type forceviewserveruse: boolean
    1085          @param forceviewserveruse: Force the use of C{ViewServer} even if the conditions to use
    1086                              C{UiAutomator} are satisfied
    1087          @type localport: int
    1088          @param localport: the local port used in the redirection
    1089          @type remoteport: int
    1090          @param remoteport: the remote port used to start the C{ViewServer} in the device or
    1091                             emulator
    1092          @type startviewserver: boolean
    1093          @param startviewserver: Whether to start the B{global} ViewServer
    1094          @type ignoreuiautomatorkilled: boolean
    1095          @param ignoreuiautomatorkilled: Ignores received B{Killed} message from C{uiautomator}
    1096          '''
    1097
    1098          if not device:
    1099              raise Exception('Device is not connected')
    1100          self.device = device
    1101          ''' The C{MonkeyDevice} device instance '''
    1102
    1103          if not serialno:
    1104              raise ValueError("Serialno cannot be None")
    1105          self.serialno = self.__mapSerialNo(serialno)
    1106          ''' The serial number of the device '''
    1107
    1108          if DEBUG_DEVICE: print >> sys.stderr, "ViewClient: using device with serialno", self.serialno
    1109
    1110          if adb:
    1111              if not os.access(adb, os.X_OK):
    1112                  raise Exception('adb="%s" is not executable' % adb)
    1113          else:
    1114              adb = ViewClient.__obtainAdbPath()
    1115
    1116          self.adb = adb
    1117          ''' The adb command '''
    1118          self.root = None
    1119          ''' The root node '''
    1120          self.viewsById = {}
    1121          ''' The map containing all the L{View}s indexed by their L{View.getUniqueId()} '''
    1122          self.display = {}
    1123          ''' The map containing the device's display properties: width, height and density '''
    1124          for prop in [ 'width', 'height', 'density' ]:
    1125              self.display[prop] = -1
    1126              if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES:
    1127                  try:
    1128                      self.display[prop] = int(device.getProperty('display.' + prop))
    1129                  except:
    1130                      if WARNINGS:
    1131                          warnings.warn("Couldn't determine display %s" % prop)
    1132              else:
    1133                  # these values are usually not defined as properties, so we stick to the -1 set
    1134                  # before
    1135                  pass
    1136
    1137          self.build = {}
    1138          ''' The map containing the device's build properties: version.sdk, version.release '''
    1139          for prop in [VERSION_SDK_PROPERTY, 'version.release']:
    1140              self.build[prop] = -1
    1141              try:
    1142                  if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES:
    1143                      self.build[prop] = device.getProperty('build.' + prop)
    1144                  else:
    1145                      self.build[prop] = device.shell('getprop ro.build.' + prop)[:-2]
    1146              except:
    1147                  if WARNINGS:
    1148                      warnings.warn("Couldn't determine build %s" % prop)
    1149
    1150              if prop == VERSION_SDK_PROPERTY:
    1151                  # we expect it to be an int
    1152                  self.build[prop] = int(self.build[prop] if self.build[prop] else -1)
    1153
    1154          self.ro = {}
    1155          ''' The map containing the device's ro properties: secure, debuggable '''
    1156          for prop in ['secure', 'debuggable']:
    1157              try:
    1158                  self.ro[prop] = device.shell('getprop ro.' + prop)[:-2]
    1159              except:
    1160                  if WARNINGS:
    1161                      warnings.warn("Couldn't determine ro %s" % prop)
    1162                  self.ro[prop] = 'UNKNOWN'
    1163
    1164          self.forceViewServerUse = forceviewserveruse
    1165          ''' Force the use of ViewServer even if the conditions to use UiAutomator are satisfied '''
    1166          self.useUiAutomator = (self.build[VERSION_SDK_PROPERTY] >= 16) and not forceviewserveruse # jelly bean 4.1 & 4.2
    1167          ''' If UIAutomator is supported by the device it will be used '''
    1168          self.ignoreUiAutomatorKilled = ignoreuiautomatorkilled
    1169          ''' On some devices (i.e. Nexus 7 running 4.2.2) uiautomator is killed just after generating
    1170          the dump file. In many cases the file is already complete so we can ask to ignore the 'Killed'
    1171          message by setting L{ignoreuiautomatorkilled} to C{True}.
    1172
    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.
    1175          '''
    1176
    1177          if self.useUiAutomator:
    1178              self.textProperty = TEXT_PROPERTY_UI_AUTOMATOR
    1179          else:
    1180              if self.build[VERSION_SDK_PROPERTY] <= 10:
    1181                  self.textProperty = TEXT_PROPERTY_API_10
    1182              else:
    1183                  self.textProperty = TEXT_PROPERTY
    1184              if startviewserver:
    1185                  if not self.serviceResponse(device.shell('service call window 3')):
    1186                      try:
    1187                          self.assertServiceResponse(device.shell('service call window 1 i32 %d' %
    1188                                                          remoteport))
    1189                      except:
    1190                          msg = 'Cannot start View server.\n' \
    1191                              'This only works on emulator and devices running developer versions.\n' \
    1192                              'Does hierarchyviewer work on your device?\n' \
    1193                              'See https://github.com/dtmilano/AndroidViewClient/wiki/Secure-mode\n\n' \
    1194                              'Device properties:\n' \
    1195                              '    ro.secure=%s\n' \
    1196                              '    ro.debuggable=%s\n' % (self.ro['secure'], self.ro['debuggable'])
    1197                          raise Exception(msg)
    1198
    1199              self.localPort = localport
    1200              self.remotePort = remoteport
    1201              # FIXME: it seems there's no way of obtaining the serialno from the MonkeyDevice
    1202              subprocess.check_call([self.adb, '-s', self.serialno, 'forward', 'tcp:%d' % self.localPort,
    1203                                      'tcp:%d' % self.remotePort])
    1204
    1205          self.windows = None
    1206          ''' The list of windows as obtained by L{ViewClient.list()} '''
    1207
    1208          if autodump:
    1209              self.dump()
    1210
    1211 -    def __del__(self):
    1212          # should clean up some things
    1213          pass
    1214
    1215      @staticmethod
    1216 -    def __obtainAdbPath():
    1217          '''
    1218          Obtains the ADB path attempting know locations for different OSs
    1219          '''
    1220
    1221          osName = java.lang.System.getProperty('os.name')
    1222          isWindows = False
    1223          if osName.startswith('Windows'):
    1224              adb = 'adb.exe'
    1225              isWindows = True
    1226          else:
    1227              adb = 'adb'
    1228
    1229          ANDROID_HOME = os.environ['ANDROID_HOME'] if os.environ.has_key('ANDROID_HOME') else '/opt/android-sdk'
    1230          HOME = os.environ['HOME'] if os.environ.has_key('HOME') else ''
    1231
    1232          possibleChoices = [ os.path.join(ANDROID_HOME, 'platform-tools', adb),
    1233                             os.path.join(HOME,  "android", 'platform-tools', adb),
    1234                             os.path.join(HOME,  "android-sdk", 'platform-tools', adb),
    1235                             adb,
    1236                             ]
    1237
    1238          if osName.startswith('Windows'):
    1239              possibleChoices.append(os.path.join("""C:\Program Files\Android\android-sdk\platform-tools""", adb))
    1240              possibleChoices.append(os.path.join("""C:\Program Files (x86)\Android\android-sdk\platform-tools""", adb))
    1241          elif osName.startswith('Linux'):
    1242              possibleChoices.append(os.path.join("opt", "android-sdk-linux",  'platform-tools', adb))
    1243              possibleChoices.append(os.path.join(HOME,  "opt", "android-sdk-linux",  'platform-tools', adb))
    1244              possibleChoices.append(os.path.join(HOME,  "android-sdk-linux",  'platform-tools', adb))
    1245          elif osName.startswith('Mac'):
    1246              possibleChoices.append(os.path.join("opt", "android-sdk-mac_x86",  'platform-tools', adb))
    1247              possibleChoices.append(os.path.join(HOME,  "opt", "android-sdk-mac", 'platform-tools', adb))
    1248              possibleChoices.append(os.path.join(HOME,  "android-sdk-mac", 'platform-tools', adb))
    1249              possibleChoices.append(os.path.join(HOME,  "opt", "android-sdk-mac_x86",  'platform-tools', adb))
    1250              possibleChoices.append(os.path.join(HOME,  "android-sdk-mac_x86",  'platform-tools', adb))
    1251          else:
    1252              # Unsupported OS
    1253              pass
    1254
    1255          for exeFile in possibleChoices:
    1256              if os.access(exeFile, os.X_OK):
    1257                  return exeFile
    1258
    1259          for path in os.environ["PATH"].split(os.pathsep):
    1260              exeFile = os.path.join(path, adb)
    1261              if exeFile != None and os.access(exeFile, os.X_OK if not isWindows else os.F_OK):
    1262                  return exeFile
    1263
    1264          raise Exception('adb="%s" is not executable. Did you forget to set ANDROID_HOME in the environment?' % adb)
    1265
    1266      @staticmethod
    1267 -    def __mapSerialNo(serialno):
    1268          serialno = serialno.strip()
    1269          #ipRE = re.compile('^\d+\.\d+.\d+.\d+$')
    1270          if IP_RE.match(serialno):
    1271              if DEBUG_DEVICE: print >>sys.stderr, "ViewClient: adding default port to serialno", serialno, ADB_DEFAULT_PORT
    1272              return serialno + ':%d' % ADB_DEFAULT_PORT
    1273
    1274          ipPortRE = re.compile('^\d+\.\d+.\d+.\d+:\d+$')
    1275          if ipPortRE.match(serialno):
    1276              # nothing to map
    1277              return serialno
    1278
    1279          if re.search("[.*()+]", serialno):
    1280              raise ValueError("Regular expression not supported as serialno in ViewClient")
    1281
    1282          return serialno
    1283
    1284      @staticmethod
    1285 -    def __obtainDeviceSerialNumber(device):
    1286          if DEBUG_DEVICE: print >>sys.stderr, "ViewClient: obtaining serial number for connected device"
    1287          serialno = device.getProperty('ro.serialno')
    1288          if not serialno:
    1289              serialno = device.shell('getprop ro.serialno')
    1290              if serialno:
    1291                  serialno = serialno[:-2]
    1292          if not serialno:
    1293              qemu = device.shell('getprop ro.kernel.qemu')
    1294              if qemu:
    1295                  qemu = qemu[:-2]
    1296                  if qemu and int(qemu) == 1:
    1297                      # FIXME !!!!!
    1298                      # this must be calculated from somewhere, though using a fixed serialno for now
    1299                      warnings.warn("Running on emulator but no serial number was specified then 'emulator-5554' is used")
    1300                      serialno = 'emulator-5554'
    1301          if not serialno:
    1302              # If there's only one device connected get its serialno
    1303              adb = ViewClient.__obtainAdbPath()
    1304              if DEBUG_DEVICE: print >>sys.stderr, "    using adb=%s" % adb
    1305              s = subprocess.Popen([adb, 'get-serialno'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={}).communicate()[0][:-1]
    1306              if s != 'unknown':
    1307                  serialno = s
    1308          if DEBUG_DEVICE: print >>sys.stderr, "    serialno=%s" % serialno
    1309          if not serialno:
    1310              warnings.warn("Couldn't obtain the serialno of the connected device")
    1311          return serialno
    1312
    1313      @staticmethod
    1314 -    def setAlarm(timeout):
    1315          osName = java.lang.System.getProperty('os.name')
    1316          if osName.startswith('Windows'): # alarm is not implemented in Windows
    1317              return
    1318          signal.alarm(timeout)
    1319
    1320      @staticmethod
    1321 -    def connectToDeviceOrExit(timeout=60, verbose=False, ignoresecuredevice=False, serialno=None):
    1322          '''
    1323          Connects to a device which serial number is obtained from the script arguments if available
    1324          or using the default regex C{.*}.
    1325
    1326          If the connection is not successful the script exits.
    1327          L{MonkeyRunner.waitForConnection()} returns a L{MonkeyDevice} even if the connection failed.
    1328          Then, to detect this situation, C{device.wake()} is attempted and if it fails then it is
    1329          assumed the previous connection failed.
    1330
    1331          @type timeout: int
    1332          @param timeout: timeout for the connection
    1333          @type verbose: bool
    1334          @param verbose: Verbose output
    1335          @type ignoresecuredevice: bool
    1336          @param ignoresecuredevice: Ignores the check for a secure device
    1337          @type serialno: str
    1338          @param serialno: The device or emulator serial number
    1339
    1340          @return: the device and serialno used for the connection
    1341          '''
    1342
    1343          progname = os.path.basename(sys.argv[0])
    1344          if serialno is None:
    1345              # eat all the extra options the invoking script may have added
    1346              args = sys.argv
    1347              while len(args) > 1 and args[1][0] == '-':
    1348                  args.pop(1)
    1349              serialno = args[1] if len(args) > 1 else \
    1350                      os.environ['ANDROID_SERIAL'] if os.environ.has_key('ANDROID_SERIAL') \
    1351                      else '.*'
    1352          if IP_RE.match(serialno):
    1353              # If matches an IP address format and port was not specified add the default
    1354              serialno += ':%d' % ADB_DEFAULT_PORT
    1355          if verbose:
    1356              print >> sys.stderr, 'Connecting to a device with serialno=%s with a timeout of %d secs...' % \
    1357                  (serialno, timeout)
    1358          # Sometimes MonkeyRunner doesn't even timeout (i.e. two connections from same process), so let's
    1359          # handle it here
    1360          ViewClient.setAlarm(timeout+5)
    1361          device = MonkeyRunner.waitForConnection(timeout, serialno)
    1362          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)
    1368          if verbose:
    1369              print >> sys.stderr, 'Connected to device with serialno=%s' % serialno
    1370          secure = device.getSystemProperty('ro.secure')
    1371          debuggable = device.getSystemProperty('ro.debuggable')
    1372          versionProperty = device.getProperty('build.' + VERSION_SDK_PROPERTY)
    1373          if versionProperty:
    1374              version = int(versionProperty)
    1375          else:
    1376              if verbose:
    1377                  print "Couldn't obtain device SDK version"
    1378              version = -1
    1379
    1380          # we are going to use UiAutomator for versions >= 16 that's why we ignore if the device
    1381          # is secure if this is true
    1382          if secure == '1' and debuggable == '0' and not ignoresecuredevice and version < 16:
    1383              print >> sys.stderr, "%s: ERROR: Device is secure, AndroidViewClient won't work." % progname
    1384              if verbose:
    1385                  print >> sys.stderr, "    secure=%s debuggable=%s version=%d ignoresecuredevice=%s" % \
    1386                      (secure, debuggable, version, ignoresecuredevice)
    1387              sys.exit(2)
    1388          if re.search("[.*()+]", serialno) and not re.search("(\d{1,3}\.){3}\d{1,3}", serialno):
    1389              # if a regex was used we have to determine the serialno used
    1390              serialno = ViewClient.__obtainDeviceSerialNumber(device)
    1391          if verbose:
    1392              print >> sys.stderr, 'Actual device serialno=%s' % serialno
    1393          return device, serialno
    1394
    1395      @staticmethod
    1396 -    def traverseShowClassIdAndText(view, extraInfo=None, noextrainfo=None):
    1397          '''
    1398          Shows the View class, id and text if available.
    1399          This function can be used as a transform function to L{ViewClient.traverse()}
    1400
    1401          @type view: I{View}
    1402          @param view: the View
    1403          @type extraInfo: method
    1404          @param extraInfo: the View method to add extra info
    1405          @type noextrainfo: bool
    1406          @param noextrainfo: Don't add extra info
    1407
    1408          @return: the string containing class, id, and text if available
    1409          '''
    1410
    1411          try:
    1412              eis = ''
    1413              if extraInfo:
    1414                  eis = extraInfo(view).__str__()
    1415                  if not eis and noextrainfo:
    1416                      eis = noextrainfo
    1417              if eis:
    1418                  eis = ' ' + eis
    1419              return "%s %s %s%s" % (view.getClass(), view.getId(), view.getText(), eis)
    1420          except Exception, e:
    1421              return "Exception in view=%s: %s" % (view.__smallStr__(), e)
    1422
    1423      @staticmethod
    1424 -    def traverseShowClassIdTextAndUniqueId(view):
    1425          '''
    1426          Shows the View class, id, text if available and unique id.
    1427          This function can be used as a transform function to L{ViewClient.traverse()}
    1428
    1429          @type view: I{View}
    1430          @param view: the View
    1431          @return: the string containing class, id, and text if available and unique Id
    1432          '''
    1433
    1434          return ViewClient.traverseShowClassIdAndText(view, View.getUniqueId)
    1435
    1436      @staticmethod
    1437 -    def traverseShowClassIdTextAndContentDescription(view):
    1438          '''
    1439          Shows the View class, id, text if available and unique id.
    1440          This function can be used as a transform function to L{ViewClient.traverse()}
    1441
    1442          @type view: I{View}
    1443          @param view: the View
    1444          @return: the string containing class, id, and text if available and the content description
    1445          '''
    1446
    1447          return ViewClient.traverseShowClassIdAndText(view, View.getContentDescription, 'NAF')
    1448
    1449      @staticmethod
    1450 -    def traverseShowClassIdTextAndCenter(view):
    1451          '''
    1452          Shows the View class, id and text if available.
    1453          This function can be used as a transform function to L{ViewClient.traverse()}
    1454
    1455          @type view: I{View}
    1456          @param view: the View
    1457          @return: the string containing class, id, and text if available
    1458          '''
    1459
    1460          return ViewClient.traverseShowClassIdAndText(view, View.getCenter)
    1461
    1462      @staticmethod
    1463 -    def traverseShowClassIdTextPositionAndSize(view):
    1464          '''
    1465          Shows the View class, id and text if available.
    1466          This function can be used as a transform function to L{ViewClient.traverse()}
    1467
    1468          @type view: I{View}
    1469          @param view: the View
    1470          @return: the string containing class, id, and text if available
    1471          '''
    1472
    1473          return ViewClient.traverseShowClassIdAndText(view, View.getPositionAndSize)
    1474
    1475      # methods that can be used to transform ViewClient.traverse output
    1476      TRAVERSE_CIT = traverseShowClassIdAndText
    1477      ''' An alias for L{traverseShowClassIdAndText(view)} '''
    1478      TRAVERSE_CITUI = traverseShowClassIdTextAndUniqueId
    1479      ''' An alias for L{traverseShowClassIdTextAndUniqueId(view)} '''
    1480      TRAVERSE_CITCD = traverseShowClassIdTextAndContentDescription
    1481      ''' An alias for L{traverseShowClassIdTextAndContentDescription(view)} '''
    1482      TRAVERSE_CITC = traverseShowClassIdTextAndCenter
    1483      ''' An alias for L{traverseShowClassIdTextAndCenter(view)} '''
    1484      TRAVERSE_CITPS = traverseShowClassIdTextPositionAndSize
    1485      ''' An alias for L{traverseShowClassIdTextPositionAndSize(view)} '''
    1486
    1487      @staticmethod
    1488 -    def sleep(secs=1.0):
    1489          '''
    1490          Sleeps for the specified number of seconds.
    1491
    1492          @type secs: float
    1493          @param secs: number of seconds
    1494          '''
    1495          time.sleep(secs)
    1496
    1497 -    def assertServiceResponse(self, response):
    1498          '''
    1499          Checks whether the response received from the server is correct or raises and Exception.
    1500
    1501          @type response: str
    1502          @param response: Response received from the server
    1503
    1504          @raise Exception: If the response received from the server is invalid
    1505          '''
    1506
    1507          if not self.serviceResponse(response):
    1508              raise Exception('Invalid response received from service.')
    1509
    1510 -    def serviceResponse(self, response):
    1511          '''
    1512          Checks the response received from the I{ViewServer}.
    1513
    1514          @return: C{True} if the response received matches L{PARCEL_TRUE}, C{False} otherwise
    1515          '''
    1516
    1517          PARCEL_TRUE = "Result: Parcel(00000000 00000001   '........')\r\n"
    1518          ''' The TRUE response parcel '''
    1519          if DEBUG:
    1520              print >>sys.stderr, "serviceResponse: comparing '%s' vs Parcel(%s)" % (response, PARCEL_TRUE)
    1521          return response == PARCEL_TRUE
    1522
    1523 -    def setViews(self, received):
    1524          '''
    1525          Sets L{self.views} to the received value splitting it into lines.
    1526
    1527          @type received: str
    1528          @param received: the string received from the I{View Server}
    1529          '''
    1530
    1531          if not received or received == "":
    1532              raise ValueError("received is empty")
    1533          self.views = []
    1534          ''' 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()}. '''
    1535          self.__parseTree(received.split("\n"))
    1536          if DEBUG:
    1537              print >>sys.stderr, "there are %d views in this dump" % len(self.views)
    1538
    1539 -    def setViewsFromUiAutomatorDump(self, received):
    1540          '''
    1541          Sets L{self.views} to the received value parsing the received XML.
    1542
    1543          @type received: str
    1544          @param received: the string received from the I{UI Automator}
    1545          '''
    1546
    1547          if not received or received == "":
    1548              raise ValueError("received is empty")
    1549          self.views = []
    1550          ''' 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()}. '''
    1551          self.__parseTreeFromUiAutomatorDump(received)
    1552          if DEBUG:
    1553              print >>sys.stderr, "there are %d views in this dump" % len(self.views)
    1554
    1555
    1556 -    def __splitAttrs(self, strArgs):
    1557          '''
    1558          Splits the C{View} attributes in C{strArgs} and optionally adds the view id to the C{viewsById} list.
    1559
    1560          Unique Ids
    1561          ==========
    1562          It is very common to find C{View}s having B{NO_ID} as the Id. This turns very difficult to
    1563          use L{self.findViewById()}. To help in this situation this method assigns B{unique Ids}.
    1564
    1565          The B{unique Ids} are generated using the pattern C{id/no_id/<number>} with C{<number>} starting
    1566          at 1.
    1567
    1568          @type strArgs: str
    1569          @param strArgs: the string containing the raw list of attributes and values
    1570
    1571          @return: Returns the attributes map.
    1572          '''
    1573
    1574          if self.useUiAutomator:
    1575              raise RuntimeError("This method is not compatible with UIAutomator")
    1576          # replace the spaces in text:mText to preserve them in later split
    1577          # they are translated back after the attribute matches
    1578          textRE = re.compile('%s=%s,' % (self.textProperty, __nd('len')))
    1579          m = textRE.search(strArgs)
    1580          if m:
    1581              __textStart = m.end()
    1582              __textLen = int(m.group('len'))
    1583              __textEnd = m.end() + __textLen
    1584              s1 = strArgs[__textStart:__textEnd]
    1585              s2 = s1.replace(' ', WS)
    1586              strArgs = strArgs.replace(s1, s2, 1)
    1587
    1588          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')))
    1591
    1592          attrs = {}
    1593          viewId = None
    1594          m = idRE.search(strArgs)
    1595          if m:
    1596              viewId = m.group('viewId')
    1597              if DEBUG:
    1598                  print >>sys.stderr, "found view with id=%s" % viewId
    1599
    1600          for attr in strArgs.split():
    1601              m = attrRE.match(attr)
    1602              if m:
    1603                  __attr = m.group('attr')
    1604                  __parens = '()' if m.group('parens') else ''
    1605                  __len = int(m.group('len'))
    1606                  __val = m.group('val')
    1607                  if WARNINGS and __len != len(__val):
    1608                      warnings.warn("Invalid len: expected: %d   found: %d   s=%s   e=%s" % (__len, len(__val), __val[:50], __val[-50:]))
    1609                  if __attr == self.textProperty:
    1610                      # restore spaces that have been replaced
    1611                      __val = __val.replace(WS, ' ')
    1612                  attrs[__attr + __parens] = __val
    1613              else:
    1614                  m = hashRE.match(attr)
    1615                  if m:
    1616                      attrs['class'] = m.group('class')
    1617                      attrs['oid'] = m.group('oid')
    1618                  else:
    1619                      if DEBUG:
    1620                          print >>sys.stderr, attr, "doesn't match"
    1621
    1622          if True: # was assignViewById
    1623              if not viewId:
    1624                  # If the view has NO_ID we are assigning a default id here (id/no_id) which is
    1625                  # immediately incremented if another view with no id was found before to generate
    1626                  # a unique id
    1627                  viewId = "id/no_id/1"
    1628              if viewId in self.viewsById:
    1629                  # sometimes the view ids are not unique, so let's generate a unique id here
    1630                  i = 1
    1631                  while True:
    1632                      newId = re.sub('/\d+$', '', viewId) + '/%d' % i
    1633                      if not newId in self.viewsById:
    1634                          break
    1635                      i += 1
    1636                  viewId = newId
    1637                  if DEBUG:
    1638                      print >>sys.stderr, "adding viewById %s" % viewId
    1639              # We are assigning a new attribute to keep the original id preserved, which could have
    1640              # been NO_ID repeated multiple times
    1641              attrs['uniqueId'] = viewId
    1642
    1643          return attrs
    1644
    1645 -    def __parseTree(self, receivedLines):
    1646          '''
    1647          Parses the View tree contained in L{receivedLines}. The tree is created and the root node assigned to L{self.root}.
    1648          This method also assigns L{self.viewsById} values using L{View.getUniqueId} as the key.
    1649
    1650          @type receivedLines: str
    1651          @param receivedLines: the string received from B{View Server}
    1652          '''
    1653
    1654          self.root = None
    1655          self.viewsById = {}
    1656          self.views = []
    1657          parent = None
    1658          parents = []
    1659          treeLevel = -1
    1660          newLevel = -1
    1661          lastView = None
    1662          for v in receivedLines:
    1663              if v == '' or v == 'DONE' or v == 'DONE.':
    1664                  break
    1665              attrs = self.__splitAttrs(v)
    1666              if not self.root:
    1667                  if v[0] == ' ':
    1668                      raise Exception("Unexpected root element starting with ' '.")
    1669                  self.root = View.factory(attrs, self.device, self.build[VERSION_SDK_PROPERTY], self.forceViewServerUse)
    1670                  if DEBUG: self.root.raw = v
    1671                  treeLevel = 0
    1672                  newLevel = 0
    1673                  lastView = self.root
    1674                  parent = self.root
    1675                  parents.append(parent)
    1676              else:
    1677                  newLevel = (len(v) - len(v.lstrip()))
    1678                  if newLevel == 0:
    1679                      raise Exception("newLevel==0 treeLevel=%d but tree can have only one root, v=%s" % (treeLevel, v))
    1680                  child = View.factory(attrs, self.device, self.build[VERSION_SDK_PROPERTY], self.forceViewServerUse)
    1681                  if DEBUG: child.raw = v
    1682                  if newLevel == treeLevel:
    1683                      parent.add(child)
    1684                      lastView = child
    1685                  elif newLevel > treeLevel:
    1686                      if (newLevel - treeLevel) != 1:
    1687                          raise Exception("newLevel jumps %d levels, v=%s" % ((newLevel-treeLevel), v))
    1688                      parent = lastView
    1689                      parents.append(parent)
    1690                      parent.add(child)
    1691                      lastView = child
    1692                      treeLevel = newLevel
    1693                  else: # newLevel < treeLevel
    1694                      for i in range(treeLevel - newLevel):
    1695                          parents.pop()
    1696                      parent = parents.pop()
    1697                      parents.append(parent)
    1698                      parent.add(child)
    1699                      treeLevel = newLevel
    1700                      lastView = child
    1701              self.views.append(lastView)
    1702              self.viewsById[lastView.getUniqueId()] = lastView
    1703
    1704 -    def __parseTreeFromUiAutomatorDump(self, receivedXml):
    1705          parser = UiAutomator2AndroidViewClient(self.device, self.build[VERSION_SDK_PROPERTY])
    1706          self.root = parser.Parse(receivedXml)
    1707          self.views = parser.views
    1708          self.viewsById = {}
    1709          for v in self.views:
    1710              self.viewsById[v.getUniqueId()] = v
    1711
    1712 -    def getRoot(self):
    1713          '''
    1714          Gets the root node of the C{View} tree
    1715
    1716          @return: the root node of the C{View} tree
    1717          '''
    1718          return self.root
    1719
    1720 -    def traverse(self, root="ROOT", indent="", transform=View.__str__, stream=sys.stdout):
    1721          '''
    1722          Traverses the C{View} tree and prints its nodes.
    1723
    1724          The nodes are printed converting them to string but other transformations can be specified
    1725          by providing a method name as the C{transform} parameter.
    1726
    1727          @type root: L{View}
    1728          @param root: the root node from where the traverse starts
    1729          @type indent: str
    1730          @param indent: the indentation string to use to print the nodes
    1731          @type transform: method
    1732          @param transform: a method to use to transform the node before is printed
    1733          '''
    1734
    1735          if type(root) == types.StringType and root == "ROOT":
    1736              root = self.root
    1737
    1738          return ViewClient.__traverse(root, indent, transform, stream)
    1739  #         if not root:
    1740  #             return
    1741  #
    1742  #         s = transform(root)
    1743  #         if s:
    1744  #             print >>stream, "%s%s" % (indent, s)
    1745  #
    1746  #         for ch in root.children:
    1747  #             self.traverse(ch, indent=indent+"   ", transform=transform, stream=stream)
    1748
    1749      @staticmethod
    1750 -    def __traverse(root, indent="", transform=View.__str__, stream=sys.stdout):
    1751          if not root:
    1752              return
    1753
    1754          s = transform(root)
    1755          if s:
    1756              us = s.encode('utf-8', 'replace')
    1757              ius = unicode("%s%s" % (indent, us), 'utf-8', 'replace')
    1758              print >>stream, ius.encode('utf-8', 'replace')
    1759
    1760          for ch in root.children:
    1761              ViewClient.__traverse(ch, indent=indent+"   ", transform=transform, stream=stream)
    1762
    1763 -    def dump(self, window=-1, sleep=1):
    1764          '''
    1765          Dumps the window content.
    1766
    1767          Sleep is useful to wait some time before obtaining the new content when something in the
    1768          window has changed.
    1769
    1770          @type window: int or str
    1771          @param window: the window id or name of the window to dump.
    1772                      The B{name} is the package name or the window name (i.e. StatusBar) for
    1773                      system windows.
    1774                      The window id can be provided as C{int} or C{str}. The C{str} should represent
    1775                      and C{int} in either base 10 or 16.
    1776                      Use -1 to dump all windows.
    1777                      This parameter only is used when the backend is B{ViewServer} and it's
    1778                      ignored for B{UiAutomator}.
    1779          @type sleep: int
    1780          @param sleep: sleep in seconds before proceeding to dump the content
    1781
    1782          @return: the list of Views as C{str} received from the server after being split into lines
    1783          '''
    1784
    1785          if sleep > 0:
    1786              MonkeyRunner.sleep(sleep)
    1787
    1788          if self.useUiAutomator:
    1789              # NOTICE:
    1790              # Using /dev/tty this works even on devices with no sdcard
    1791              received = self.device.shell('uiautomator dump /dev/tty >/dev/null')
    1792              if not received:
    1793                  raise RuntimeError('ERROR: Empty UiAutomator dump was received')
    1794              received = received.encode('utf-8', 'ignore')
    1795              if DEBUG:
    1796                  self.received = received
    1797              if DEBUG_RECEIVED:
    1798                  print >>sys.stderr, "received %d chars" % len(received)
    1799                  print >>sys.stderr
    1800                  print >>sys.stderr, repr(received)
    1801                  print >>sys.stderr
    1802              onlyKilledRE = re.compile('[\n\S]*Killed[\n\r\S]*', re.MULTILINE)
    1803              if onlyKilledRE.search(received):
    1804                  raise RuntimeError('''ERROR: UiAutomator output contains no valid information. UiAutomator was killed, no reason given.''')
    1805              if self.ignoreUiAutomatorKilled:
    1806                  if DEBUG_RECEIVED:
    1807                      print >>sys.stderr, "ignoring UiAutomator Killed"
    1808                  killedRE = re.compile('</hierarchy>[\n\S]*Killed', re.MULTILINE)
    1809                  if killedRE.search(received):
    1810                      received = re.sub(killedRE, '</hierarchy>', received)
    1811                  elif DEBUG_RECEIVED:
    1812                      print "UiAutomator Killed: NOT FOUND!"
    1813                  # It seems that API18 uiautomator spits this message to stdout
    1814                  dumpedToDevTtyRE = re.compile('</hierarchy>[\n\S]*UI hierchary dumped to: /dev/tty.*', re.MULTILINE)
    1815                  if dumpedToDevTtyRE.search(received):
    1816                      received = re.sub(dumpedToDevTtyRE, '</hierarchy>', received)
    1817                  if DEBUG_RECEIVED:
    1818                      print >>sys.stderr, "received=", received
    1819              if re.search('\[: not found', received):
    1820                  raise RuntimeError('''ERROR: Some emulator images (i.e. android 4.1.2 API 16 generic_x86) does not include the '[' command.
    1821  While UiAutomator back-end might be supported 'uiautomator' command fails.
    1822  You should force ViewServer back-end.''')
    1823              self.setViewsFromUiAutomatorDump(received)
    1824          else:
    1825              if isinstance(window, str):
    1826                  self.list(sleep=0)
    1827                  found = False
    1828                  for wId in self.windows:
    1829                      try:
    1830                          if window == self.windows[wId]:
    1831                              window = wId
    1832                              found = True
    1833                              break
    1834                      except:
    1835                          pass
    1836                      try:
    1837                          if int(window) == wId:
    1838                              window = wId
    1839                              found = True
    1840                              break
    1841                      except:
    1842                          pass
    1843                      try:
    1844                          if int(window, 16) == wId:
    1845                              window = wId
    1846                              found = True
    1847                              break
    1848                      except:
    1849                          pass
    1850
    1851                  if not found:
    1852                      raise RuntimeError("ERROR: Cannot find window '%s' in %s" % (window, self.windows))
    1853
    1854              s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    1855              try:
    1856                  s.connect((VIEW_SERVER_HOST, self.localPort))
    1857              except socket.error, ex:
    1858                  raise RuntimeError("ERROR: Connecting to %s:%d: %s" % (VIEW_SERVER_HOST, self.localPort, ex))
    1859              cmd = 'dump %x\r\n' % window
    1860              if DEBUG:
    1861                  print >>sys.stderr, "executing: '%s'" % cmd
    1862              s.send(cmd)
    1863              received = ""
    1864              doneRE = re.compile("DONE")
    1865              ViewClient.setAlarm(120)
    1866              while True:
    1867                  if DEBUG_RECEIVED:
    1868                      print >>sys.stderr, "    reading from socket..."
    1869                  received += s.recv(1024)
    1870                  if doneRE.search(received[-7:]):
    1871                      break
    1872              s.close()
    1873              ViewClient.setAlarm(0)
    1874              if DEBUG:
    1875                  self.received = received
    1876              if DEBUG_RECEIVED:
    1877                  print >>sys.stderr, "received %d chars" % len(received)
    1878                  print >>sys.stderr
    1879                  print >>sys.stderr, received
    1880                  print >>sys.stderr
    1881              self.setViews(received)
    1882
    1883              if DEBUG_TREE:
    1884                  self.traverse(self.root)
    1885
    1886          return self.views
    1887
    1888 -    def list(self, sleep=1):
    1889          '''
    1890          List the windows.
    1891
    1892          Sleep is useful to wait some time before obtaining the new content when something in the
    1893          window has changed.
    1894          This also sets L{self.windows} as the list of windows.
    1895
    1896          @type sleep: int
    1897          @param sleep: sleep in seconds before proceeding to dump the content
    1898
    1899          @return: the list of windows
    1900          '''
    1901
    1902          if sleep > 0:
    1903              MonkeyRunner.sleep(sleep)
    1904
    1905          if self.useUiAutomator:
    1906              raise Exception("Not implemented yet: listing windows with UiAutomator")
    1907          else:
    1908              s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    1909              try:
    1910                  s.connect((VIEW_SERVER_HOST, self.localPort))
    1911              except socket.error, ex:
    1912                  raise RuntimeError("ERROR: Connecting to %s:%d: %s" % (VIEW_SERVER_HOST, self.localPort, ex))
    1913              s.send('list\r\n')
    1914              received = ""
    1915              doneRE = re.compile("DONE")
    1916              while True:
    1917                  received += s.recv(1024)
    1918                  if doneRE.search(received[-7:]):
    1919                      break
    1920              s.close()
    1921              if DEBUG:
    1922                  self.received = received
    1923              if DEBUG_RECEIVED:
    1924                  print >>sys.stderr, "received %d chars" % len(received)
    1925                  print >>sys.stderr
    1926                  print >>sys.stderr, received
    1927                  print >>sys.stderr
    1928
    1929              self.windows = {}
    1930              for line in received.split('\n'):
    1931                  if not line:
    1932                      break
    1933                  if doneRE.search(line):
    1934                      break
    1935                  (wid, package) = line.split()
    1936                  self.windows[int('0x' + wid, 16)] = package
    1937              return self.windows
    1938
    1939
    1940 -    def findViewById(self, viewId, root="ROOT", viewFilter=None):
    1941          '''
    1942          Finds the View with the specified viewId.
    1943
    1944          @type viewId: str
    1945          @param viewId: the ID of the view to find
    1946          @type root: str
    1947          @type root: View
    1948          @param root: the root node of the tree where the View will be searched
    1949          @type: viewFilter: function
    1950          @param viewFilter: a function that will be invoked providing the candidate View as a parameter
    1951                             and depending on the return value (C{True} or C{False}) the View will be
    1952                             selected and returned as the result of C{findViewById()} or ignored.
    1953                             This can be C{None} and no extra filtering is applied.
    1954
    1955          @return: the C{View} found or C{None}
    1956          '''
    1957
    1958          if not root:
    1959              return None
    1960
    1961          if type(root) == types.StringType and root == "ROOT":
    1962              return self.findViewById(viewId, self.root, viewFilter)
    1963
    1964          if root.getId() == viewId:
    1965              if viewFilter:
    1966                  if viewFilter(root):
    1967                      return root
    1968              else:
    1969                  return root
    1970
    1971          if re.match('^id/no_id', viewId) or re.match('^id/.+/.+', viewId):
    1972              if root.getUniqueId() == viewId:
    1973                  if viewFilter:
    1974                      if viewFilter(root):
    1975                          return root;
    1976                  else:
    1977                      return root
    1978
    1979
    1980          for ch in root.children:
    1981              foundView = self.findViewById(viewId, ch, viewFilter)
    1982              if foundView:
    1983                  if viewFilter:
    1984                      if viewFilter(foundView):
    1985                          return foundView
    1986                  else:
    1987                      return foundView
    1988
    1989 -    def findViewByIdOrRaise(self, viewId, root="ROOT", viewFilter=None):
    1990          '''
    1991          Finds the View or raise a ViewNotFoundException.
    1992
    1993          @type viewId: str
    1994          @param viewId: the ID of the view to find
    1995          @type root: str
    1996          @type root: View
    1997          @param root: the root node of the tree where the View will be searched
    1998          @type: viewFilter: function
    1999          @param viewFilter: a function that will be invoked providing the candidate View as a parameter
    2000                             and depending on the return value (C{True} or C{False}) the View will be
    2001                             selected and returned as the result of C{findViewById()} or ignored.
    2002                             This can be C{None} and no extra filtering is applied.
    2003          @return: the View found
    2004          @raise ViewNotFoundException: raise the exception if View not found
    2005          '''
    2006
    2007          view = self.findViewById(viewId, root, viewFilter)
    2008          if view:
    2009              return view
    2010          else:
    2011              raise ViewNotFoundException("ID", viewId, root)
    2012
    2013 -    def findViewByTag(self, tag, root="ROOT"):
    2014          '''
    2015          Finds the View with the specified tag
    2016          '''
    2017
    2018          return self.findViewWithAttribute('getTag()', tag, root)
    2019
    2020 -    def findViewByTagOrRaise(self, tag, root="ROOT"):
    2021          '''
    2022          Finds the View with the specified tag or raise a ViewNotFoundException
    2023          '''
    2024
    2025          view = self.findViewWithAttribute('getTag()', tag, root)
    2026          if view:
    2027              return view
    2028          else:
    2029              raise ViewNotFoundException("tag", tag, root)
    2030
    2031 -    def __findViewWithAttributeInTree(self, attr, val, root):
    2032          if not self.root:
    2033              print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
    2034              return None
    2035
    2036          if type(root) == types.StringType and root == "ROOT":
    2037              root = self.root
    2038
    2039          if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: type val=", type(val)
    2040          if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: checking if root=%s has attr=%s == %s" % (root.__smallStr__(), attr, val)
    2041
    2042          if isinstance(val, org.python.modules.sre.PatternObject):
    2043              return self.__findViewWithAttributeInTreeThatMatches(attr, val, root)
    2044          else:
    2045              if root and attr in root.map and root.map[attr] == val:
    2046                  if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree:  FOUND: %s" % root.__smallStr__()
    2047                  return root
    2048              else:
    2049                  for ch in root.children:
    2050                      v = self.__findViewWithAttributeInTree(attr, val, ch)
    2051                      if v:
    2052                          return v
    2053
    2054          return None
    2055
    2056 -    def __findViewWithAttributeInTreeOrRaise(self, attr, val, root):
    2057          view = self.__findViewWithAttributeInTree(attr, val, root)
    2058          if view:
    2059              return view
    2060          else:
    2061              raise ViewNotFoundException(attr, val, root)
    2062
    2063 -    def __findViewWithAttributeInTreeThatMatches(self, attr, regex, root, rlist=[]):
    2064          if not self.root:
    2065              print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
    2066              return None
    2067
    2068          if type(root) == types.StringType and root == "ROOT":
    2069              root = self.root
    2070
    2071          if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTreeThatMatches: checking if root=%s attr=%s matches %s" % (root.__smallStr__(), attr, regex)
    2072
    2073          if root and attr in root.map and regex.match(root.map[attr]):
    2074              if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTreeThatMatches:  FOUND: %s" % root.__smallStr__()
    2075              return root
    2076              #print >>sys.stderr, "appending root=%s to rlist=%s" % (root.__smallStr__(), rlist)
    2077              #return rlist.append(root)
    2078          else:
    2079              for ch in root.children:
    2080                  v = self.__findViewWithAttributeInTreeThatMatches(attr, regex, ch, rlist)
    2081                  if v:
    2082                      return v
    2083                      #print >>sys.stderr, "appending v=%s to rlist=%s" % (v.__smallStr__(), rlist)
    2084                      #return rlist.append(v)
    2085
    2086          return None
    2087          #return rlist
    2088
    2089 -    def findViewWithAttribute(self, attr, val, root="ROOT"):
    2090          '''
    2091          Finds the View with the specified attribute and value
    2092          '''
    2093
    2094          return self.__findViewWithAttributeInTree(attr, val, root)
    2095
    2096 -    def findViewWithAttributeOrRaise(self, attr, val, root="ROOT"):
    2097          '''
    2098          Finds the View or raise a ViewNotFoundException.
    2099
    2100          @return: the View found
    2101          @raise ViewNotFoundException: raise the exception if View not found
    2102          '''
    2103
    2104          view = self.findViewWithAttribute(attr, val, root)
    2105          if view:
    2106              return view
    2107          else:
    2108              raise ViewNotFoundException(attr, val, root)
    2109
    2110 -    def findViewWithAttributeThatMatches(self, attr, regex, root="ROOT"):
    2111          '''
    2112          Finds the list of Views with the specified attribute matching
    2113          regex
    2114          '''
    2115
    2116          return self.__findViewWithAttributeInTreeThatMatches(attr, regex, root)
    2117
    2118 -    def findViewWithText(self, text, root="ROOT"):
    2119          if DEBUG:
    2120              print >>sys.stderr, "findViewWithText(%s, %s)" % (text, root)
    2121          if isinstance(text, org.python.modules.sre.PatternObject):
    2122              return self.findViewWithAttributeThatMatches(self.textProperty, text, root)
    2123              #l = self.findViewWithAttributeThatMatches(TEXT_PROPERTY, text)
    2124              #ll = len(l)
    2125              #if ll == 0:
    2126              #    return None
    2127              #elif ll == 1:
    2128              #    return l[0]
    2129              #else:
    2130              #    print >>sys.stderr, "WARNING: findViewWithAttributeThatMatches invoked by findViewWithText returns %d items." % ll
    2131              #    return l
    2132          else:
    2133              return self.findViewWithAttribute(self.textProperty, text, root)
    2134
    2135 -    def findViewWithTextOrRaise(self, text, root="ROOT"):
    2136          '''
    2137          Finds the View or raise a ViewNotFoundException.
    2138
    2139          @return: the View found
    2140          @raise ViewNotFoundException: raise the exception if View not found
    2141          '''
    2142
    2143          if DEBUG:
    2144              print >>sys.stderr, "findViewWithTextOrRaise(%s, %s)" % (text, root)
    2145          view = self.findViewWithText(text, root)
    2146          if view:
    2147              return view
    2148          else:
    2149              raise ViewNotFoundException("text", text, root)
    2150
    2151 -    def findViewWithContentDescription(self, contentdescription, root="ROOT"):
    2152          '''
    2153          Finds the View with the specified content description
    2154          '''
    2155
    2156          return self.__findViewWithAttributeInTree('content-desc', contentdescription, root)
    2157
    2158 -    def findViewWithContentDescriptionOrRaise(self, contentdescription, root="ROOT"):
    2159          '''
    2160          Finds the View with the specified content description
    2161          '''
    2162
    2163          return self.__findViewWithAttributeInTreeOrRaise('content-desc', contentdescription, root)
    2164
    2165 -    def findViewsContainingPoint(self, (x, y), filter=None):
    2166          '''
    2167          Finds the list of Views that contain the point (x, y).
    2168          '''
    2169
    2170          if not filter:
    2171              filter = lambda v: True
    2172
    2173          return [v for v in self.views if (v.containsPoint((x,y)) and filter(v))]
    2174
    2175 -    def getViewIds(self):
    2176          '''
    2177          @deprecated: Use L{getViewsById} instead.
    2178
    2179          Returns the Views map.
    2180          '''
    2181
    2182          return self.viewsById
    2183
    2184 -    def getViewsById(self):
    2185          '''
    2186          Returns the Views map. The keys are C{uniqueIds} and the values are C{View}s.
    2187          '''
    2188
    2189          return self.viewsById
    2190
    2191 -    def __getFocusedWindowPosition(self):
    2192          return self.__getFocusedWindowId()
    2193
    2194 -    def getSdkVersion(self):
    2195          '''
    2196          Gets the SDK version.
    2197          '''
    2198
    2199          return self.build[VERSION_SDK_PROPERTY]
    2200
    2201 -    def isKeyboardShown(self):
    2202          '''
    2203          Whether the keyboard is displayed.
    2204          '''
    2205
    2206          dim = self.device.shell('dumpsys input_method')
    2207          if dim:
    2208              # FIXME: API >= 15 ?
    2209              return "mInputShown=true" in dim
    2210          return False
    2211
    2212 -    def writeImageToFile(self, filename, format="PNG"):
    2213          '''
    2214          Write the View image to the specified filename in the specified format.
    2215
    2216          @type filename: str
    2217          @param filename: Absolute path and optional filename receiving the image. If this points to
    2218                           a directory, then the filename is determined by the serialno of the device and
    2219                           format extension.
    2220          @type format: str
    2221          @param format: Image format (default format is PNG)
    2222          '''
    2223
    2224          if not os.path.isabs(filename):
    2225              raise ValueError("writeImageToFile expects an absolute path")
    2226          if os.path.isdir(filename):
    2227              filename = os.path.join(filename, self.serialno + '.' + format.lower())
    2228          if DEBUG:
    2229              print >> sys.stderr, "writeImageToFile: saving image to '%s' in %s format" % (filename, format)
    2230          self.device.takeSnapshot().writeToFile(filename, format)
    2231
    2232      @staticmethod
    2233 -    def __pickleable(tree):
    2234          '''
    2235          Makes the tree pickleable.
    2236          '''
    2237
    2238          def removeDeviceReference(view):
    2239              '''
    2240              Removes the reference to a L{MonkeyDevice}.
    2241              '''
    2242
    2243              view.device = None
    2244
    2245          ###########################################################################################
    2246          # FIXME: Unfortunatelly deepcopy does not work with MonkeyDevice objects, which is
    2247          # sadly the reason why we cannot pickle the tree and we need to remove the MonkeyDevice
    2248          # references.
    2249          # We wanted to copy the tree to preserve the original and make piclkleable the copy.
    2250          #treeCopy = copy.deepcopy(tree)
    2251          treeCopy = tree
    2252          # IMPORTANT:
    2253          # This assumes that the first element in the list is the tree root
    2254          ViewClient.__traverse(treeCopy[0], transform=removeDeviceReference)
    2255          ###########################################################################################
    2256          return treeCopy
    2257
    2258 -    def distance(self, tree):
    2259          '''
    2260          Calculates the distance between this tree and the tree passed as argument.
    2261
    2262          @type tree: list of Views
    2263          @param tree: Tree of Views
    2264          @return: the distance
    2265          '''
    2266          ################################################################
    2267          #FIXME: this should copy the entire tree and then transform it #
    2268          ################################################################
    2269          pickleableViews = ViewClient.__pickleable(self.views)
    2270          pickleableTree = ViewClient.__pickleable(tree)
    2271          s1 = pickle.dumps(pickleableViews)
    2272          s2 = pickle.dumps(pickleableTree)
    2273
    2274          if DEBUG_DISTANCE:
    2275              print >>sys.stderr, "distance: calculating distance between", s1[:20], "and", s2[:20]
    2276
    2277          l1 = len(s1)
    2278          l2 = len(s2)
    2279          t = float(max(l1, l2))
    2280
    2281          if l1 == l2:
    2282              if DEBUG_DISTANCE:
    2283                  print >>sys.stderr, "distance: trees have same length, using Hamming distance"
    2284              return ViewClient.__hammingDistance(s1, s2)/t
    2285          else:
    2286              if DEBUG_DISTANCE:
    2287                  print >>sys.stderr, "distance: trees have different length, using Levenshtein distance"
    2288              return ViewClient.__levenshteinDistance(s1, s2)/t
    2289
    2290      @staticmethod
    2291 -    def __hammingDistance(s1, s2):
    2292          '''
    2293          Finds the Hamming distance between two strings.
    2294
    2295          @param s1: string
    2296          @param s2: string
    2297          @return: the distance
    2298          @raise ValueError: if the lenght of the strings differ
    2299          '''
    2300
    2301          l1 = len(s1)
    2302          l2 = len(s2)
    2303
    2304          if l1 != l2:
    2305              raise ValueError("Hamming distance requires strings of same size.")
    2306
    2307          return sum(ch1 != ch2 for ch1, ch2 in zip(s1, s2))
    2308
    2309 -    def hammingDistance(self, tree):
    2310          '''
    2311          Finds the Hamming distance between this tree and the one passed as argument.
    2312          '''
    2313
    2314          s1 = ' '.join(map(View.__str__, self.views))
    2315          s2 = ' '.join(map(View.__str__, tree))
    2316
    2317          return ViewClient.__hammingDistance(s1, s2)
    2318
    2319      @staticmethod
    2320 -    def __levenshteinDistance(s, t):
    2321          '''
    2322          Find the Levenshtein distance between two Strings.
    2323
    2324          Python version of Levenshtein distance method implemented in Java at
    2325          U{http://www.java2s.com/Code/Java/Data-Type/FindtheLevenshteindistancebetweentwoStrings.htm}.
    2326
    2327          This is the number of changes needed to change one String into
    2328          another, where each change is a single character modification (deletion,
    2329          insertion or substitution).
    2330
    2331          The previous implementation of the Levenshtein distance algorithm
    2332          was from U{http://www.merriampark.com/ld.htm}
    2333
    2334          Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError
    2335          which can occur when my Java implementation is used with very large strings.
    2336          This implementation of the Levenshtein distance algorithm
    2337          is from U{http://www.merriampark.com/ldjava.htm}::
    2338
    2339              StringUtils.getLevenshteinDistance(null, *)             = IllegalArgumentException
    2340              StringUtils.getLevenshteinDistance(*, null)             = IllegalArgumentException
    2341              StringUtils.getLevenshteinDistance("","")               = 0
    2342              StringUtils.getLevenshteinDistance("","a")              = 1
    2343              StringUtils.getLevenshteinDistance("aaapppp", "")       = 7
    2344              StringUtils.getLevenshteinDistance("frog", "fog")       = 1
    2345              StringUtils.getLevenshteinDistance("fly", "ant")        = 3
    2346              StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
    2347              StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
    2348              StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
    2349              StringUtils.getLevenshteinDistance("hello", "hallo")    = 1
    2350
    2351          @param s:  the first String, must not be null
    2352          @param t:  the second String, must not be null
    2353          @return: result distance
    2354          @raise ValueError: if either String input C{null}
    2355          '''
    2356          if s is None or t is None:
    2357              raise ValueError("Strings must not be null")
    2358
    2359          n = len(s)
    2360          m = len(t)
    2361
    2362          if n == 0:
    2363              return m
    2364          elif m == 0:
    2365              return n
    2366
    2367          if n > m:
    2368              tmp = s
    2369              s = t
    2370              t = tmp
    2371              n = m;
    2372              m = len(t)
    2373
    2374          p = [None]*(n+1)
    2375          d = [None]*(n+1)
    2376
    2377          for i in range(0, n+1):
    2378              p[i] = i
    2379
    2380          for j in range(1, m+1):
    2381              if DEBUG_DISTANCE:
    2382                  if j % 100 == 0:
    2383                      print >>sys.stderr, "DEBUG:", int(j/(m+1.0)*100),"%\r",
    2384              t_j = t[j-1]
    2385              d[0] = j
    2386
    2387              for i in range(1, n+1):
    2388                  cost = 0 if s[i-1] == t_j else 1
    2389                  #  minimum of cell to the left+1, to the top+1, diagonally left and up +cost
    2390                  d[i] = min(min(d[i-1]+1, p[i]+1), p[i-1]+cost)
    2391
    2392              _d = p
    2393              p = d
    2394              d = _d
    2395
    2396          if DEBUG_DISTANCE:
    2397              print >> sys.stderr, "\n"
    2398          return p[n]
    2399
    2400 -    def levenshteinDistance(self, tree):
    2401          '''
    2402          Finds the Levenshtein distance between this tree and the one passed as argument.
    2403          '''
    2404
    2405          s1 = ' '.join(map(View.__microStr__, self.views))
    2406          s2 = ' '.join(map(View.__microStr__, tree))
    2407
    2408          return ViewClient.__levenshteinDistance(s1, s2)
    2409
    2410      @staticmethod
    2411 -    def excerpt(str, execute=False):
    2412          code = Excerpt2Code().Parse(str)
    2413          if execute:
    2414              exec code
    2415          else:
    2416              return code
    2417
    2418
    2419  if __name__ == "__main__":
    2420      try:
    2421          vc = ViewClient(None)
    2422      except:
    2423          print "%s: Don't expect this to do anything" % __file__
    2424