viewclient.py 176 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Copyright (C) 2012-2015 Diego Torres Milano
  4. Created on Feb 2, 2012
  5. Licensed under the Apache License, Version 2.0 (the "License");
  6. you may not use this file except in compliance with the License.
  7. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. @author: Diego Torres Milano
  15. '''
  16. __version__ = '10.3.1'
  17. import sys
  18. import warnings
  19. if sys.executable:
  20. if 'monkeyrunner' in sys.executable:
  21. warnings.warn(
  22. '''
  23. You should use a 'python' interpreter, not 'monkeyrunner' for this module
  24. ''', RuntimeWarning)
  25. import subprocess
  26. import re
  27. import socket
  28. import os
  29. import types
  30. import time
  31. import signal
  32. import copy
  33. import pickle
  34. import platform
  35. import xml.parsers.expat
  36. import unittest
  37. from com.dtmilano.android.common import _nd, _nh, _ns, obtainPxPy, obtainVxVy,\
  38. obtainVwVh, obtainAdbPath
  39. from com.dtmilano.android.window import Window
  40. from com.dtmilano.android.adb import adbclient
  41. DEBUG = False
  42. DEBUG_DEVICE = DEBUG and False
  43. DEBUG_RECEIVED = DEBUG and False
  44. DEBUG_TREE = DEBUG and False
  45. DEBUG_GETATTR = DEBUG and False
  46. DEBUG_CALL = DEBUG and False
  47. DEBUG_COORDS = DEBUG and False
  48. DEBUG_TOUCH = DEBUG and False
  49. DEBUG_STATUSBAR = DEBUG and False
  50. DEBUG_WINDOWS = DEBUG and False
  51. DEBUG_BOUNDS = DEBUG and False
  52. DEBUG_DISTANCE = DEBUG and False
  53. DEBUG_MULTI = DEBUG and False
  54. DEBUG_VIEW = DEBUG and False
  55. DEBUG_VIEW_FACTORY = DEBUG and False
  56. WARNINGS = False
  57. VIEW_SERVER_HOST = 'localhost'
  58. VIEW_SERVER_PORT = 4939
  59. ADB_DEFAULT_PORT = 5555
  60. OFFSET = 25
  61. ''' This assumes the smallest touchable view on the screen is approximately 50px x 50px
  62. and touches it at M{(x+OFFSET, y+OFFSET)} '''
  63. USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES = True
  64. ''' Use C{AdbClient} to obtain the needed properties. If this is
  65. C{False} then C{adb shell getprop} is used '''
  66. USE_PHYSICAL_DISPLAY_INFO = True
  67. ''' Use C{dumpsys display} to obtain display properties. If this is
  68. C{False} then C{USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES} is used '''
  69. SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED = False
  70. ''' Skips some classes related with the Action Bar and the PhoneWindow$DecorView in the
  71. coordinates calculation
  72. @see: L{View.getXY()} '''
  73. VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED = False
  74. ''' Under some conditions the touch event should be longer [t(DOWN) << t(UP)]. C{True} enables a
  75. workaround to delay the events.'''
  76. # some device properties
  77. VERSION_SDK_PROPERTY = 'ro.build.version.sdk'
  78. VERSION_RELEASE_PROPERTY = 'ro.build.version.release'
  79. # some constants for the attributes
  80. ID_PROPERTY = 'mID'
  81. ID_PROPERTY_UI_AUTOMATOR = 'uniqueId'
  82. TEXT_PROPERTY = 'text:mText'
  83. TEXT_PROPERTY_API_10 = 'mText'
  84. TEXT_PROPERTY_UI_AUTOMATOR = 'text'
  85. WS = u"\xfe" # the whitespace replacement char for TEXT_PROPERTY
  86. TAG_PROPERTY = 'getTag()'
  87. LEFT_PROPERTY = 'layout:mLeft'
  88. LEFT_PROPERTY_API_8 = 'mLeft'
  89. TOP_PROPERTY = 'layout:mTop'
  90. TOP_PROPERTY_API_8 = 'mTop'
  91. WIDTH_PROPERTY = 'layout:getWidth()'
  92. WIDTH_PROPERTY_API_8 = 'getWidth()'
  93. HEIGHT_PROPERTY = 'layout:getHeight()'
  94. HEIGHT_PROPERTY_API_8 = 'getHeight()'
  95. GET_VISIBILITY_PROPERTY = 'getVisibility()'
  96. LAYOUT_TOP_MARGIN_PROPERTY = 'layout:layout_topMargin'
  97. IS_FOCUSED_PROPERTY_UI_AUTOMATOR = 'focused'
  98. IS_FOCUSED_PROPERTY = 'focus:isFocused()'
  99. # visibility
  100. VISIBLE = 0x0
  101. INVISIBLE = 0x4
  102. GONE = 0x8
  103. RegexType = type(re.compile(''))
  104. IP_RE = re.compile('^(\d{1,3}\.){3}\d{1,3}$')
  105. ID_RE = re.compile('id/([^/]*)(/(\d+))?')
  106. class ViewNotFoundException(Exception):
  107. '''
  108. ViewNotFoundException is raised when a View is not found.
  109. '''
  110. def __init__(self, attr, value, root):
  111. if isinstance(value, RegexType):
  112. msg = "Couldn't find View with %s that matches '%s' in tree with root=%s" % (attr, value.pattern, root)
  113. else:
  114. msg = "Couldn't find View with %s='%s' in tree with root=%s" % (attr, value, root)
  115. super(Exception, self).__init__(msg)
  116. class View:
  117. '''
  118. View class
  119. '''
  120. @staticmethod
  121. def factory(arg1, arg2, version=-1, forceviewserveruse=False, windowId=None):
  122. '''
  123. View factory
  124. @type arg1: ClassType or dict
  125. @type arg2: View instance or AdbClient
  126. '''
  127. if DEBUG_VIEW_FACTORY:
  128. print >> sys.stderr, "View.factory(%s, %s, %s, %s)" % (arg1, arg2, version, forceviewserveruse)
  129. if type(arg1) == types.ClassType:
  130. cls = arg1
  131. attrs = None
  132. else:
  133. cls = None
  134. attrs = arg1
  135. if isinstance(arg2, View):
  136. view = arg2
  137. device = None
  138. else:
  139. device = arg2
  140. view = None
  141. if attrs and attrs.has_key('class'):
  142. clazz = attrs['class']
  143. if DEBUG_VIEW_FACTORY:
  144. print >> sys.stderr, " View.factory: creating View with specific class: %s" % clazz
  145. if clazz == 'android.widget.TextView':
  146. return TextView(attrs, device, version, forceviewserveruse, windowId)
  147. elif clazz == 'android.widget.EditText':
  148. return EditText(attrs, device, version, forceviewserveruse, windowId)
  149. elif clazz == 'android.widget.ListView':
  150. return ListView(attrs, device, version, forceviewserveruse, windowId)
  151. else:
  152. return View(attrs, device, version, forceviewserveruse, windowId)
  153. elif cls:
  154. if view:
  155. return cls.__copy(view)
  156. else:
  157. return cls(attrs, device, version, forceviewserveruse, windowId)
  158. elif view:
  159. return copy.copy(view)
  160. else:
  161. if DEBUG_VIEW_FACTORY:
  162. print >> sys.stderr, " View.factory: creating generic View"
  163. return View(attrs, device, version, forceviewserveruse, windowId)
  164. @classmethod
  165. def __copy(cls, view):
  166. '''
  167. Copy constructor
  168. '''
  169. return cls(view.map, view.device, view.version, view.forceviewserveruse, view.windowId)
  170. def __init__(self, _map, device, version=-1, forceviewserveruse=False, windowId=None):
  171. '''
  172. Constructor
  173. @type _map: map
  174. @param _map: the map containing the (attribute, value) pairs
  175. @type device: AdbClient
  176. @param device: the device containing this View
  177. @type version: int
  178. @param version: the Android SDK version number of the platform where this View belongs. If
  179. this is C{-1} then the Android SDK version will be obtained in this
  180. constructor.
  181. @type forceviewserveruse: boolean
  182. @param forceviewserveruse: Force the use of C{ViewServer} even if the conditions were given
  183. to use C{UiAutomator}.
  184. '''
  185. if DEBUG_VIEW:
  186. print >> sys.stderr, "View.__init__(%s, %s, %s, %s)" % ("map" if _map is not None else None, device, version, forceviewserveruse)
  187. if _map:
  188. print >> sys.stderr, " map:", type(_map)
  189. for attr, val in _map.iteritems():
  190. if len(val) > 50:
  191. val = val[:50] + "..."
  192. print >> sys.stderr, " %s=%s" % (attr, val)
  193. self.map = _map
  194. ''' The map that contains the C{attr},C{value} pairs '''
  195. self.device = device
  196. ''' The AdbClient '''
  197. self.children = []
  198. ''' The children of this View '''
  199. self.parent = None
  200. ''' The parent of this View '''
  201. self.windows = {}
  202. self.currentFocus = None
  203. ''' The current focus '''
  204. self.windowId = windowId
  205. ''' The window this view resides '''
  206. self.build = {}
  207. ''' Build properties '''
  208. self.version = version
  209. ''' API version number '''
  210. self.forceviewserveruse = forceviewserveruse
  211. ''' Force ViewServer use '''
  212. self.uiScrollable = None
  213. ''' If this is a scrollable View this keeps the L{UiScrollable} object '''
  214. self.target = False
  215. ''' Is this a touch target zone '''
  216. if version != -1:
  217. self.build[VERSION_SDK_PROPERTY] = version
  218. else:
  219. try:
  220. if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES:
  221. self.build[VERSION_SDK_PROPERTY] = int(device.getProperty(VERSION_SDK_PROPERTY))
  222. else:
  223. self.build[VERSION_SDK_PROPERTY] = int(device.shell('getprop ' + VERSION_SDK_PROPERTY)[:-2])
  224. except:
  225. self.build[VERSION_SDK_PROPERTY] = -1
  226. version = self.build[VERSION_SDK_PROPERTY]
  227. self.useUiAutomator = (version >= 16) and not forceviewserveruse
  228. ''' Whether to use UIAutomator or ViewServer '''
  229. self.idProperty = None
  230. ''' The id property depending on the View attribute format '''
  231. self.textProperty = None
  232. ''' The text property depending on the View attribute format '''
  233. self.tagProperty = None
  234. ''' The tag property depending on the View attribute format '''
  235. self.leftProperty = None
  236. ''' The left property depending on the View attribute format '''
  237. self.topProperty = None
  238. ''' The top property depending on the View attribute format '''
  239. self.widthProperty = None
  240. ''' The width property depending on the View attribute format '''
  241. self.heightProperty = None
  242. ''' The height property depending on the View attribute format '''
  243. self.isFocusedProperty = None
  244. ''' The focused property depending on the View attribute format '''
  245. if version >= 16 and self.useUiAutomator:
  246. self.idProperty = ID_PROPERTY_UI_AUTOMATOR
  247. self.textProperty = TEXT_PROPERTY_UI_AUTOMATOR
  248. self.leftProperty = LEFT_PROPERTY
  249. self.topProperty = TOP_PROPERTY
  250. self.widthProperty = WIDTH_PROPERTY
  251. self.heightProperty = HEIGHT_PROPERTY
  252. self.isFocusedProperty = IS_FOCUSED_PROPERTY_UI_AUTOMATOR
  253. elif version > 10 and (version < 16 or self.useUiAutomator):
  254. self.idProperty = ID_PROPERTY
  255. self.textProperty = TEXT_PROPERTY
  256. self.tagProperty = TAG_PROPERTY
  257. self.leftProperty = LEFT_PROPERTY
  258. self.topProperty = TOP_PROPERTY
  259. self.widthProperty = WIDTH_PROPERTY
  260. self.heightProperty = HEIGHT_PROPERTY
  261. self.isFocusedProperty = IS_FOCUSED_PROPERTY
  262. elif version == 10:
  263. self.idProperty = ID_PROPERTY
  264. self.textProperty = TEXT_PROPERTY_API_10
  265. self.tagProperty = TAG_PROPERTY
  266. self.leftProperty = LEFT_PROPERTY
  267. self.topProperty = TOP_PROPERTY
  268. self.widthProperty = WIDTH_PROPERTY
  269. self.heightProperty = HEIGHT_PROPERTY
  270. self.isFocusedProperty = IS_FOCUSED_PROPERTY
  271. elif version >= 7 and version < 10:
  272. self.idProperty = ID_PROPERTY
  273. self.textProperty = TEXT_PROPERTY_API_10
  274. self.tagProperty = TAG_PROPERTY
  275. self.leftProperty = LEFT_PROPERTY_API_8
  276. self.topProperty = TOP_PROPERTY_API_8
  277. self.widthProperty = WIDTH_PROPERTY_API_8
  278. self.heightProperty = HEIGHT_PROPERTY_API_8
  279. self.isFocusedProperty = IS_FOCUSED_PROPERTY
  280. elif version > 0 and version < 7:
  281. self.idProperty = ID_PROPERTY
  282. self.textProperty = TEXT_PROPERTY_API_10
  283. self.tagProperty = TAG_PROPERTY
  284. self.leftProperty = LEFT_PROPERTY
  285. self.topProperty = TOP_PROPERTY
  286. self.widthProperty = WIDTH_PROPERTY
  287. self.heightProperty = HEIGHT_PROPERTY
  288. self.isFocusedProperty = IS_FOCUSED_PROPERTY
  289. elif version == -1:
  290. self.idProperty = ID_PROPERTY
  291. self.textProperty = TEXT_PROPERTY
  292. self.tagProperty = TAG_PROPERTY
  293. self.leftProperty = LEFT_PROPERTY
  294. self.topProperty = TOP_PROPERTY
  295. self.widthProperty = WIDTH_PROPERTY
  296. self.heightProperty = HEIGHT_PROPERTY
  297. self.isFocusedProperty = IS_FOCUSED_PROPERTY
  298. else:
  299. self.idProperty = ID_PROPERTY
  300. self.textProperty = TEXT_PROPERTY
  301. self.tagProperty = TAG_PROPERTY
  302. self.leftProperty = LEFT_PROPERTY
  303. self.topProperty = TOP_PROPERTY
  304. self.widthProperty = WIDTH_PROPERTY
  305. self.heightProperty = HEIGHT_PROPERTY
  306. self.isFocusedProperty = IS_FOCUSED_PROPERTY
  307. try:
  308. if self.isScrollable():
  309. self.uiScrollable = UiScrollable(self)
  310. except AttributeError:
  311. pass
  312. def __getitem__(self, key):
  313. return self.map[key]
  314. def __getattr__(self, name):
  315. if DEBUG_GETATTR:
  316. print >>sys.stderr, "__getattr__(%s) version: %d" % (name, self.build[VERSION_SDK_PROPERTY])
  317. # NOTE:
  318. # I should try to see if 'name' is a defined method
  319. # but it seems that if I call locals() here an infinite loop is entered
  320. if self.map.has_key(name):
  321. r = self.map[name]
  322. elif self.map.has_key(name + '()'):
  323. # the method names are stored in the map with their trailing '()'
  324. r = self.map[name + '()']
  325. elif name.count("_") > 0:
  326. mangledList = self.allPossibleNamesWithColon(name)
  327. mangledName = self.intersection(mangledList, self.map.keys())
  328. if len(mangledName) > 0 and self.map.has_key(mangledName[0]):
  329. r = self.map[mangledName[0]]
  330. else:
  331. # Default behavior
  332. raise AttributeError, name
  333. elif name.startswith('is'):
  334. # try removing 'is' prefix
  335. if DEBUG_GETATTR:
  336. print >> sys.stderr, " __getattr__: trying without 'is' prefix"
  337. suffix = name[2:].lower()
  338. if self.map.has_key(suffix):
  339. r = self.map[suffix]
  340. else:
  341. # Default behavior
  342. raise AttributeError, name
  343. elif name.startswith('get'):
  344. # try removing 'get' prefix
  345. if DEBUG_GETATTR:
  346. print >> sys.stderr, " __getattr__: trying without 'get' prefix"
  347. suffix = name[3:].lower()
  348. if self.map.has_key(suffix):
  349. r = self.map[suffix]
  350. else:
  351. # Default behavior
  352. raise AttributeError, name
  353. elif name == 'getResourceId':
  354. if DEBUG_GETATTR:
  355. print >> sys.stderr, " __getattr__: getResourceId"
  356. if self.map.has_key('resource-id'):
  357. r = self.map['resource-id']
  358. else:
  359. # Default behavior
  360. raise AttributeError, name
  361. else:
  362. # Default behavior
  363. raise AttributeError, name
  364. # if the method name starts with 'is' let's assume its return value is boolean
  365. # if name[:2] == 'is':
  366. # r = True if r == 'true' else False
  367. if r == 'true':
  368. r = True
  369. elif r == 'false':
  370. r = False
  371. # this should not cached in some way
  372. def innerMethod():
  373. if DEBUG_GETATTR:
  374. print >>sys.stderr, "innerMethod: %s returning %s" % (innerMethod.__name__, r)
  375. return r
  376. innerMethod.__name__ = name
  377. # this should work, but then there's problems with the arguments of innerMethod
  378. # even if innerMethod(self) is added
  379. #setattr(View, innerMethod.__name__, innerMethod)
  380. #setattr(self, innerMethod.__name__, innerMethod)
  381. return innerMethod
  382. def __call__(self, *args, **kwargs):
  383. if DEBUG_CALL:
  384. print >>sys.stderr, "__call__(%s)" % (args if args else None)
  385. def getClass(self):
  386. '''
  387. Gets the L{View} class
  388. @return: the L{View} class or C{None} if not defined
  389. '''
  390. try:
  391. return self.map['class']
  392. except:
  393. return None
  394. def getId(self):
  395. '''
  396. Gets the L{View} Id
  397. @return: the L{View} C{Id} or C{None} if not defined
  398. @see: L{getUniqueId()}
  399. '''
  400. try:
  401. return self.map['resource-id']
  402. except:
  403. pass
  404. try:
  405. return self.map[self.idProperty]
  406. except:
  407. return None
  408. def getContentDescription(self):
  409. '''
  410. Gets the content description.
  411. '''
  412. try:
  413. return self.map['content-desc']
  414. except:
  415. return None
  416. def getTag(self):
  417. '''
  418. Gets the tag.
  419. '''
  420. try:
  421. return self.map[self.tagProperty]
  422. except:
  423. return None
  424. def getParent(self):
  425. '''
  426. Gets the parent.
  427. '''
  428. return self.parent
  429. def getChildren(self):
  430. '''
  431. Gets the children of this L{View}.
  432. '''
  433. return self.children
  434. def getText(self):
  435. '''
  436. Gets the text attribute.
  437. @return: the text attribute or C{None} if not defined
  438. '''
  439. try:
  440. return self.map[self.textProperty]
  441. except Exception:
  442. return None
  443. def getHeight(self):
  444. '''
  445. Gets the height.
  446. '''
  447. if self.useUiAutomator:
  448. return self.map['bounds'][1][1] - self.map['bounds'][0][1]
  449. else:
  450. try:
  451. return int(self.map[self.heightProperty])
  452. except:
  453. return 0
  454. def getWidth(self):
  455. '''
  456. Gets the width.
  457. '''
  458. if self.useUiAutomator:
  459. return self.map['bounds'][1][0] - self.map['bounds'][0][0]
  460. else:
  461. try:
  462. return int(self.map[self.widthProperty])
  463. except:
  464. return 0
  465. def getUniqueId(self):
  466. '''
  467. Gets the unique Id of this View.
  468. @see: L{ViewClient.__splitAttrs()} for a discussion on B{Unique Ids}
  469. '''
  470. try:
  471. return self.map['uniqueId']
  472. except:
  473. return None
  474. def getVisibility(self):
  475. '''
  476. Gets the View visibility
  477. '''
  478. try:
  479. if self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
  480. return VISIBLE
  481. elif self.map[GET_VISIBILITY_PROPERTY] == 'INVISIBLE':
  482. return INVISIBLE
  483. elif self.map[GET_VISIBILITY_PROPERTY] == 'GONE':
  484. return GONE
  485. else:
  486. return -2
  487. except:
  488. return -1
  489. def getX(self):
  490. '''
  491. Gets the View X coordinate
  492. '''
  493. return self.getXY()[0]
  494. def __getX(self):
  495. '''
  496. Gets the View X coordinate
  497. '''
  498. if DEBUG_COORDS:
  499. print >>sys.stderr, "getX(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
  500. x = 0
  501. if self.useUiAutomator:
  502. x = self.map['bounds'][0][0]
  503. else:
  504. try:
  505. if GET_VISIBILITY_PROPERTY in self.map and self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
  506. _x = int(self.map[self.leftProperty])
  507. if DEBUG_COORDS: print >>sys.stderr, " getX: VISIBLE adding %d" % _x
  508. x += _x
  509. except:
  510. warnings.warn("View %s has no '%s' property" % (self.getId(), self.leftProperty))
  511. if DEBUG_COORDS: print >>sys.stderr, " getX: returning %d" % (x)
  512. return x
  513. def getY(self):
  514. '''
  515. Gets the View Y coordinate
  516. '''
  517. return self.getXY()[1]
  518. def __getY(self):
  519. '''
  520. Gets the View Y coordinate
  521. '''
  522. if DEBUG_COORDS:
  523. print >>sys.stderr, "getY(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
  524. y = 0
  525. if self.useUiAutomator:
  526. y = self.map['bounds'][0][1]
  527. else:
  528. try:
  529. if GET_VISIBILITY_PROPERTY in self.map and self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
  530. _y = int(self.map[self.topProperty])
  531. if DEBUG_COORDS: print >>sys.stderr, " getY: VISIBLE adding %d" % _y
  532. y += _y
  533. except:
  534. warnings.warn("View %s has no '%s' property" % (self.getId(), self.topProperty))
  535. if DEBUG_COORDS: print >>sys.stderr, " getY: returning %d" % (y)
  536. return y
  537. def getXY(self, debug=False):
  538. '''
  539. Returns the I{screen} coordinates of this C{View}.
  540. WARNING: Don't call self.getX() or self.getY() inside this method
  541. or it will enter an infinite loop
  542. @return: The I{screen} coordinates of this C{View}
  543. '''
  544. if DEBUG_COORDS or debug:
  545. try:
  546. _id = self.getId()
  547. except:
  548. _id = "NO_ID"
  549. print >> sys.stderr, "getXY(%s %s ## %s)" % (self.getClass(), _id, self.getUniqueId())
  550. x = self.__getX()
  551. y = self.__getY()
  552. if self.useUiAutomator:
  553. return (x, y)
  554. parent = self.parent
  555. if DEBUG_COORDS: print >> sys.stderr, " getXY: x=%s y=%s parent=%s" % (x, y, parent.getUniqueId() if parent else "None")
  556. hx = 0
  557. ''' Hierarchy accumulated X '''
  558. hy = 0
  559. ''' Hierarchy accumulated Y '''
  560. if DEBUG_COORDS: print >> sys.stderr, " getXY: not using UiAutomator, calculating parent coordinates"
  561. while parent != None:
  562. if DEBUG_COORDS: print >> sys.stderr, " getXY: parent: %s %s <<<<" % (parent.getClass(), parent.getId())
  563. if SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED:
  564. if parent.getClass() in [ 'com.android.internal.widget.ActionBarView',
  565. 'com.android.internal.widget.ActionBarContextView',
  566. 'com.android.internal.view.menu.ActionMenuView',
  567. 'com.android.internal.policy.impl.PhoneWindow$DecorView' ]:
  568. if DEBUG_COORDS: print >> sys.stderr, " getXY: skipping %s %s (%d,%d)" % (parent.getClass(), parent.getId(), parent.__getX(), parent.__getY())
  569. parent = parent.parent
  570. continue
  571. if DEBUG_COORDS: print >> sys.stderr, " getXY: parent=%s x=%d hx=%d y=%d hy=%d" % (parent.getId(), x, hx, y, hy)
  572. hx += parent.__getX()
  573. hy += parent.__getY()
  574. parent = parent.parent
  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. if self.windowId:
  580. fw = self.windows[self.windowId]
  581. else:
  582. fw = self.windows[self.currentFocus]
  583. if DEBUG_STATUSBAR:
  584. print >> sys.stderr, " getXY: focused window=", fw
  585. 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)
  586. except KeyError:
  587. fw = None
  588. (sbw, sbh) = self.__obtainStatusBarDimensionsIfVisible()
  589. if DEBUG_COORDS or debug:
  590. print >>sys.stderr, " getXY: sb=(%d, %d) (statusbar dimensions)" % (sbw, sbh)
  591. statusBarOffset = 0
  592. pwx = 0
  593. pwy = 0
  594. if fw:
  595. if DEBUG_COORDS:
  596. print >>sys.stderr, " getXY: focused window=", fw, "sb=", (sbw, sbh)
  597. if fw.wvy <= sbh: # it's very unlikely that fw.wvy < sbh, that is a window over the statusbar
  598. if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: yes, considering offset=", sbh
  599. statusBarOffset = sbh
  600. else:
  601. if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: no, ignoring statusbar offset fw.wvy=", fw.wvy, ">", sbh
  602. if fw.py == fw.wvy:
  603. if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: but wait, fw.py == fw.wvy so we are adjusting by ", (fw.px, fw.py)
  604. pwx = fw.px
  605. pwy = fw.py
  606. else:
  607. if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: fw.py=%d <= fw.wvy=%d, no adjustment" % (fw.py, fw.wvy)
  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. def getCoords(self):
  614. '''
  615. Gets the coords of the View
  616. @return: A tuple containing the View's coordinates ((L, T), (R, B))
  617. '''
  618. if DEBUG_COORDS:
  619. print >>sys.stderr, "getCoords(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
  620. (x, y) = self.getXY();
  621. w = self.getWidth()
  622. h = self.getHeight()
  623. return ((x, y), (x+w, y+h))
  624. def getPositionAndSize(self):
  625. '''
  626. Gets the position and size (X,Y, W, H)
  627. @return: A tuple containing the View's coordinates (X, Y, W, H)
  628. '''
  629. (x, y) = self.getXY();
  630. w = self.getWidth()
  631. h = self.getHeight()
  632. return (x, y, w, h)
  633. def getBounds(self):
  634. '''
  635. Gets the View bounds
  636. '''
  637. if 'bounds' in self.map:
  638. return self.map['bounds']
  639. else:
  640. return self.getCoords()
  641. def getCenter(self):
  642. '''
  643. Gets the center coords of the View
  644. @author: U{Dean Morin <https://github.com/deanmorin>}
  645. '''
  646. (left, top), (right, bottom) = self.getCoords()
  647. x = left + (right - left) / 2
  648. y = top + (bottom - top) / 2
  649. return (x, y)
  650. def __obtainStatusBarDimensionsIfVisible(self):
  651. sbw = 0
  652. sbh = 0
  653. for winId in self.windows:
  654. w = self.windows[winId]
  655. if DEBUG_COORDS: print >> sys.stderr, " __obtainStatusBarDimensionsIfVisible: w=", w, " w.activity=", w.activity, "%%%"
  656. if w.activity == 'StatusBar':
  657. if w.wvy == 0 and w.visibility == 0:
  658. if DEBUG_COORDS: print >> sys.stderr, " __obtainStatusBarDimensionsIfVisible: statusBar=", (w.wvw, w.wvh)
  659. sbw = w.wvw
  660. sbh = w.wvh
  661. break
  662. return (sbw, sbh)
  663. def __obtainVxVy(self, m):
  664. return obtainVxVy(m)
  665. def __obtainVwVh(self, m):
  666. return obtainVwVh(m)
  667. def __obtainPxPy(self, m):
  668. return obtainPxPy(m)
  669. def __dumpWindowsInformation(self, debug=False):
  670. self.windows = {}
  671. self.currentFocus = None
  672. dww = self.device.shell('dumpsys window windows')
  673. if DEBUG_WINDOWS or debug: print >> sys.stderr, dww
  674. lines = dww.splitlines()
  675. widRE = re.compile('^ *Window #%s Window{%s (u\d+ )?%s?.*}:' %
  676. (_nd('num'), _nh('winId'), _ns('activity', greedy=True)))
  677. currentFocusRE = re.compile('^ mCurrentFocus=Window{%s .*' % _nh('winId'))
  678. viewVisibilityRE = re.compile(' mViewVisibility=0x%s ' % _nh('visibility'))
  679. # This is for 4.0.4 API-15
  680. containingFrameRE = re.compile('^ *mContainingFrame=\[%s,%s\]\[%s,%s\] mParentFrame=\[%s,%s\]\[%s,%s\]' %
  681. (_nd('cx'), _nd('cy'), _nd('cw'), _nd('ch'), _nd('px'), _nd('py'), _nd('pw'), _nd('ph')))
  682. contentFrameRE = re.compile('^ *mContentFrame=\[%s,%s\]\[%s,%s\] mVisibleFrame=\[%s,%s\]\[%s,%s\]' %
  683. (_nd('x'), _nd('y'), _nd('w'), _nd('h'), _nd('vx'), _nd('vy'), _nd('vx1'), _nd('vy1')))
  684. # This is for 4.1 API-16
  685. framesRE = re.compile('^ *Frames: containing=\[%s,%s\]\[%s,%s\] parent=\[%s,%s\]\[%s,%s\]' %
  686. (_nd('cx'), _nd('cy'), _nd('cw'), _nd('ch'), _nd('px'), _nd('py'), _nd('pw'), _nd('ph')))
  687. contentRE = re.compile('^ *content=\[%s,%s\]\[%s,%s\] visible=\[%s,%s\]\[%s,%s\]' %
  688. (_nd('x'), _nd('y'), _nd('w'), _nd('h'), _nd('vx'), _nd('vy'), _nd('vx1'), _nd('vy1')))
  689. policyVisibilityRE = re.compile('mPolicyVisibility=%s ' % _ns('policyVisibility', greedy=True))
  690. for l in range(len(lines)):
  691. m = widRE.search(lines[l])
  692. if m:
  693. num = int(m.group('num'))
  694. winId = m.group('winId')
  695. activity = m.group('activity')
  696. wvx = 0
  697. wvy = 0
  698. wvw = 0
  699. wvh = 0
  700. px = 0
  701. py = 0
  702. visibility = -1
  703. policyVisibility = 0x0
  704. for l2 in range(l+1, len(lines)):
  705. m = widRE.search(lines[l2])
  706. if m:
  707. l += (l2-1)
  708. break
  709. m = viewVisibilityRE.search(lines[l2])
  710. if m:
  711. visibility = int(m.group('visibility'))
  712. if DEBUG_COORDS: print >> sys.stderr, "__dumpWindowsInformation: visibility=", visibility
  713. if self.build[VERSION_SDK_PROPERTY] >= 17:
  714. m = framesRE.search(lines[l2])
  715. if m:
  716. px, py = obtainPxPy(m)
  717. m = contentRE.search(lines[l2+2])
  718. if m:
  719. wvx, wvy = obtainVxVy(m)
  720. wvw, wvh = obtainVwVh(m)
  721. elif self.build[VERSION_SDK_PROPERTY] >= 16:
  722. m = framesRE.search(lines[l2])
  723. if m:
  724. px, py = self.__obtainPxPy(m)
  725. m = contentRE.search(lines[l2+1])
  726. if m:
  727. # FIXME: the information provided by 'dumpsys window windows' in 4.2.1 (API 16)
  728. # when there's a system dialog may not be correct and causes the View coordinates
  729. # be offset by this amount, see
  730. # https://github.com/dtmilano/AndroidViewClient/issues/29
  731. wvx, wvy = self.__obtainVxVy(m)
  732. wvw, wvh = self.__obtainVwVh(m)
  733. elif self.build[VERSION_SDK_PROPERTY] == 15:
  734. m = containingFrameRE.search(lines[l2])
  735. if m:
  736. px, py = self.__obtainPxPy(m)
  737. m = contentFrameRE.search(lines[l2+1])
  738. if m:
  739. wvx, wvy = self.__obtainVxVy(m)
  740. wvw, wvh = self.__obtainVwVh(m)
  741. elif self.build[VERSION_SDK_PROPERTY] == 10:
  742. m = containingFrameRE.search(lines[l2])
  743. if m:
  744. px, py = self.__obtainPxPy(m)
  745. m = contentFrameRE.search(lines[l2+1])
  746. if m:
  747. wvx, wvy = self.__obtainVxVy(m)
  748. wvw, wvh = self.__obtainVwVh(m)
  749. else:
  750. warnings.warn("Unsupported Android version %d" % self.build[VERSION_SDK_PROPERTY])
  751. #print >> sys.stderr, "Searching policyVisibility in", lines[l2]
  752. m = policyVisibilityRE.search(lines[l2])
  753. if m:
  754. policyVisibility = 0x0 if m.group('policyVisibility') == 'true' else 0x8
  755. self.windows[winId] = Window(num, winId, activity, wvx, wvy, wvw, wvh, px, py, visibility + policyVisibility)
  756. else:
  757. m = currentFocusRE.search(lines[l])
  758. if m:
  759. self.currentFocus = m.group('winId')
  760. if self.windowId and self.windowId in self.windows and self.windows[self.windowId].visibility == 0:
  761. w = self.windows[self.windowId]
  762. return (w.wvx, w.wvy)
  763. elif self.currentFocus in self.windows and self.windows[self.currentFocus].visibility == 0:
  764. if DEBUG_COORDS or debug:
  765. print >> sys.stderr, "__dumpWindowsInformation: focus=", self.currentFocus
  766. print >> sys.stderr, "__dumpWindowsInformation:", self.windows[self.currentFocus]
  767. w = self.windows[self.currentFocus]
  768. return (w.wvx, w.wvy)
  769. else:
  770. if DEBUG_COORDS: print >> sys.stderr, "__dumpWindowsInformation: (0,0)"
  771. return (0,0)
  772. def touch(self, eventType=adbclient.DOWN_AND_UP, deltaX=0, deltaY=0):
  773. '''
  774. Touches the center of this C{View}. The touch can be displaced from the center by
  775. using C{deltaX} and C{deltaY} values.
  776. @param eventType: The event type
  777. @type eventType: L{adbclient.DOWN}, L{adbclient.UP} or L{adbclient.DOWN_AND_UP}
  778. @param deltaX: Displacement from center (X axis)
  779. @type deltaX: int
  780. @param deltaY: Displacement from center (Y axis)
  781. @type deltaY: int
  782. '''
  783. (x, y) = self.getCenter()
  784. if deltaX:
  785. x += deltaX
  786. if deltaY:
  787. y += deltaY
  788. if DEBUG_TOUCH:
  789. print >>sys.stderr, "should touch @ (%d, %d)" % (x, y)
  790. if VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED and eventType == adbclient.DOWN_AND_UP:
  791. if WARNINGS:
  792. print >> sys.stderr, "ViewClient: touch workaround enabled"
  793. self.device.touch(x, y, eventType=adbclient.DOWN)
  794. time.sleep(50/1000.0)
  795. self.device.touch(x+10, y+10, eventType=adbclient.UP)
  796. else:
  797. self.device.touch(x, y, eventType=eventType)
  798. def longTouch(self, duration=2000):
  799. '''
  800. Long touches this C{View}
  801. @param duration: duration in ms
  802. '''
  803. (x, y) = self.getCenter()
  804. # FIXME: get orientation
  805. self.device.longTouch(x, y, duration, orientation=-1)
  806. def allPossibleNamesWithColon(self, name):
  807. l = []
  808. for _ in range(name.count("_")):
  809. name = name.replace("_", ":", 1)
  810. l.append(name)
  811. return l
  812. def intersection(self, l1, l2):
  813. return list(set(l1) & set(l2))
  814. def containsPoint(self, (x, y)):
  815. (X, Y, W, H) = self.getPositionAndSize()
  816. return (((x >= X) and (x <= (X+W)) and ((y >= Y) and (y <= (Y+H)))))
  817. def add(self, child):
  818. '''
  819. Adds a child
  820. @type child: View
  821. @param child: The child to add
  822. '''
  823. child.parent = self
  824. self.children.append(child)
  825. def isClickable(self):
  826. return self.__getattr__('isClickable')()
  827. def isFocused(self):
  828. '''
  829. Gets the focused value
  830. @return: the focused value. If the property cannot be found returns C{False}
  831. '''
  832. try:
  833. return True if self.map[self.isFocusedProperty].lower() == 'true' else False
  834. except Exception:
  835. return False
  836. def variableNameFromId(self):
  837. _id = self.getId()
  838. if _id:
  839. var = _id.replace('.', '_').replace(':', '___').replace('/', '_')
  840. else:
  841. _id = self.getUniqueId()
  842. m = ID_RE.match(_id)
  843. if m:
  844. var = m.group(1)
  845. if m.group(3):
  846. var += m.group(3)
  847. if re.match('^\d', var):
  848. var = 'id_' + var
  849. return var
  850. def setTarget(self, target):
  851. self.target = target
  852. def isTarget(self):
  853. return self.target
  854. def writeImageToFile(self, filename, _format="PNG"):
  855. '''
  856. Write the View image to the specified filename in the specified format.
  857. @type filename: str
  858. @param filename: Absolute path and optional filename receiving the image. If this points to
  859. a directory, then the filename is determined by this View unique ID and
  860. format extension.
  861. @type _format: str
  862. @param _format: Image format (default format is PNG)
  863. '''
  864. if not os.path.isabs(filename):
  865. raise ValueError("writeImageToFile expects an absolute path (fielname='%s')" % filename)
  866. if os.path.isdir(filename):
  867. filename = os.path.join(filename, self.variableNameFromId() + '.' + _format.lower())
  868. if DEBUG:
  869. print >> sys.stderr, "writeImageToFile: saving image to '%s' in %s format" % (filename, _format)
  870. #self.device.takeSnapshot().getSubImage(self.getPositionAndSize()).writeToFile(filename, _format)
  871. # crop:
  872. # im.crop(box) ⇒ image
  873. # Returns a copy of a rectangular region from the current image.
  874. # The box is a 4-tuple defining the left, upper, right, and lower pixel coordinate.
  875. ((l, t), (r, b)) = self.getCoords()
  876. box = (l, t, r, b)
  877. if DEBUG:
  878. print >> sys.stderr, "writeImageToFile: cropping", box, " reconnect=", self.device.reconnect
  879. self.device.takeSnapshot(reconnect=self.device.reconnect).crop(box).save(filename, _format)
  880. def __smallStr__(self):
  881. __str = unicode("View[", 'utf-8', 'replace')
  882. if "class" in self.map:
  883. __str += " class=" + self.map['class']
  884. __str += " id=%s" % self.getId()
  885. __str += " ] parent="
  886. if self.parent and "class" in self.parent.map:
  887. __str += "%s" % self.parent.map["class"]
  888. else:
  889. __str += "None"
  890. return __str
  891. def __tinyStr__(self):
  892. __str = unicode("View[", 'utf-8', 'replace')
  893. if "class" in self.map:
  894. __str += " class=" + re.sub('.*\.', '', self.map['class'])
  895. __str += " id=%s" % self.getId()
  896. __str += " ]"
  897. return __str
  898. def __microStr__(self):
  899. __str = unicode('', 'utf-8', 'replace')
  900. if "class" in self.map:
  901. __str += re.sub('.*\.', '', self.map['class'])
  902. _id = self.getId().replace('id/no_id/', '-')
  903. __str += _id
  904. ((L, T), (R, B)) = self.getCoords()
  905. __str += '@%04d%04d%04d%04d' % (L, T, R, B)
  906. __str += ''
  907. return __str
  908. def __str__(self):
  909. __str = unicode("View[", 'utf-8', 'replace')
  910. if "class" in self.map:
  911. __str += " class=" + self.map["class"].__str__() + " "
  912. for a in self.map:
  913. __str += a + "="
  914. # decode() works only on python's 8-bit strings
  915. if isinstance(self.map[a], unicode):
  916. __str += self.map[a]
  917. else:
  918. __str += unicode(str(self.map[a]), 'utf-8', errors='replace')
  919. __str += " "
  920. __str += "] parent="
  921. if self.parent:
  922. if "class" in self.parent.map:
  923. __str += "%s" % self.parent.map["class"]
  924. else:
  925. __str += self.parent.getId().__str__()
  926. else:
  927. __str += "None"
  928. return __str
  929. class TextView(View):
  930. '''
  931. TextView class.
  932. '''
  933. pass
  934. class EditText(TextView):
  935. '''
  936. EditText class.
  937. '''
  938. def type(self, text, alreadyTouched=False):
  939. if not alreadyTouched:
  940. self.touch()
  941. time.sleep(0.5)
  942. escaped = text.replace('%s', '\\%s')
  943. encoded = escaped.replace(' ', '%s')
  944. self.device.type(encoded)
  945. time.sleep(0.5)
  946. def setText(self, text):
  947. """
  948. This function makes sure that any previously entered text is deleted before
  949. setting the value of the field.
  950. """
  951. if self.text() == text:
  952. return
  953. self.touch()
  954. guardrail = 0
  955. maxSize = len(self.text()) + 1
  956. while maxSize > guardrail:
  957. guardrail += 1
  958. self.device.press('KEYCODE_DEL', adbclient.DOWN_AND_UP)
  959. self.device.press('KEYCODE_FORWARD_DEL', adbclient.DOWN_AND_UP)
  960. self.type(text, alreadyTouched=True)
  961. def backspace(self):
  962. self.touch()
  963. time.sleep(1)
  964. self.device.press('KEYCODE_DEL', adbclient.DOWN_AND_UP)
  965. class UiDevice():
  966. '''
  967. Provides access to state information about the device. You can also use this class to simulate
  968. user actions on the device, such as pressing the d-pad or pressing the Home and Menu buttons.
  969. '''
  970. def __init__(self, vc):
  971. self.vc = vc
  972. self.device = self.vc.device
  973. def openNotification(self):
  974. '''
  975. Opens the notification shade.
  976. '''
  977. # the tablet has a different Notification/Quick Settings bar depending on x
  978. w13 = self.device.display['width'] / 3
  979. s = (w13, 0)
  980. e = (w13, self.device.display['height']/2)
  981. self.device.drag(s, e, 500, 20, -1)
  982. self.vc.sleep(1)
  983. self.vc.dump(-1)
  984. def openQuickSettings(self):
  985. '''
  986. Opens the Quick Settings shade.
  987. '''
  988. # the tablet has a different Notification/Quick Settings bar depending on x
  989. w23 = 2 * self.device.display['width'] / 3
  990. s = (w23, 0)
  991. e = (w23, self.device.display['height']/2)
  992. self.device.drag(s, e, 500, 20, -1)
  993. self.vc.sleep(1)
  994. if self.vc.getSdkVersion() >= 20:
  995. self.device.drag(s, e, 500, 20, -1)
  996. self.vc.sleep(1)
  997. self.vc.dump(-1)
  998. def openQuickSettingsSettings(self):
  999. '''
  1000. Opens the Quick Settings shade and then tries to open Settings from there.
  1001. '''
  1002. STATUS_BAR_SETTINGS_SETTINGS_BUTTON = [
  1003. u"Settings", u"Cài đặt", u"Instellingen", u"Կարգավորումներ", u"设置", u"Nastavitve", u"සැකසීම්", u"Ayarlar",
  1004. u"Setelan", u"Настройки", u"تنظیمات", u"Mga Setting", u"Тохиргоо", u"Configuració", u"Setări", u"Налады",
  1005. u"Einstellungen", u"პარამეტრები", u"सेटिङहरू", u"Կարգավորումներ", u"Nustatymai", u"Beállítások", u"設定",
  1006. u"सेटिंग", u"Настройки", u"Inställningar", u"設定", u"ການຕັ້ງຄ່າ", u"Configurações", u"Tetapan", u"설정",
  1007. u"ការ​កំណត់", u"Ajustes", u"הגדרות", u"Ustawienia", u"Nastavení", u"Ρυθμίσεις", u"Тохиргоо", u"Ayarlar",
  1008. u"Indstillinger", u"Налаштування", u"Mipangilio", u"Izilungiselelo", u"設定", u"Nastavenia", u"Paramètres",
  1009. u"ቅንብሮች", u"การตั้งค่า", u"Seaded", u"Iestatījumi", u"Innstillinger", u"Подешавања", u"الإعدادات", u"සැකසීම්",
  1010. u"Definições", u"Configuración", u"პარამეტრები", u"Postavke", u"Ayarlar", u"Impostazioni", u"Asetukset",
  1011. u"Instellings", u"Seaded", u"ការ​កំណត់", u"सेटिङहरू", u"Tetapan"
  1012. ]
  1013. self.openQuickSettings()
  1014. # this works on API >= 20
  1015. found = False
  1016. for s in STATUS_BAR_SETTINGS_SETTINGS_BUTTON:
  1017. if DEBUG:
  1018. print >> sys.stderr, u"finding view with cd=", type(s)
  1019. view = self.vc.findViewWithContentDescription(u'''{0}'''.format(s))
  1020. if view:
  1021. found = True
  1022. view.touch()
  1023. break
  1024. if not found:
  1025. # for previous APIs, let's find the text
  1026. for s in STATUS_BAR_SETTINGS_SETTINGS_BUTTON:
  1027. if DEBUG:
  1028. print >> sys.stderr, "s=", type(s)
  1029. try:
  1030. print >> sys.stderr, "finding view with text=", u'''{0}'''.format(s)
  1031. except:
  1032. pass
  1033. view = self.vc.findViewWithText(s)
  1034. if view:
  1035. found = True
  1036. view.touch()
  1037. break
  1038. if not found:
  1039. raise ViewNotFoundException("content-description", "'Settings' or text 'Settings'", "ROOT")
  1040. self.vc.sleep(1)
  1041. self.vc.dump(window=-1)
  1042. def changeLanguage(self, languageTo):
  1043. LANGUAGE_SETTINGS = {
  1044. "en": u"Language & input",
  1045. "af": u"Taal en invoer",
  1046. "am": u"ቋንቋ እና ግቤት",
  1047. "ar": u"اللغة والإدخال",
  1048. "az": u"Dil və daxiletmə",
  1049. "az-rAZ": u"Dil və daxiletmə",
  1050. "be": u"Мова і ўвод",
  1051. "bg": u"Език и въвеждане",
  1052. "ca": u"Idioma i introducció de text",
  1053. "cs": u"Jazyk a zadávání",
  1054. "da": u"Sprog og input",
  1055. "de": u"Sprache & Eingabe",
  1056. "el": u"Γλώσσα και εισαγωγή",
  1057. "en-rGB": u"Language & input",
  1058. "en-rIN": u"Language & input",
  1059. "es": u"Idioma e introducción de texto",
  1060. "es-rUS": u"Teclado e idioma",
  1061. "et": u"Keeled ja sisestamine",
  1062. "et-rEE": u"Keeled ja sisestamine",
  1063. "fa": u"زبان و ورود اطلاعات",
  1064. "fi": u"Kieli ja syöttötapa",
  1065. "fr": u"Langue et saisie",
  1066. "fr-rCA": u"Langue et saisie",
  1067. "hi": u"भाषा और अक्षर",
  1068. "hr": u"Jezik i ulaz",
  1069. "hu": u"Nyelv és bevitel",
  1070. "hy": u"Լեզվի & ներմուծում",
  1071. "hy-rAM": u"Լեզու և ներմուծում",
  1072. "in": u"Bahasa & masukan",
  1073. "it": u"Lingua e immissione",
  1074. "iw": u"שפה וקלט",
  1075. "ja": u"言語と入力",
  1076. "ka": u"ენისა და შეყვანის პარამეტრები",
  1077. "ka-rGE": u"ენისა და შეყვანის პარამეტრები",
  1078. "km": u"ភាសា & ការ​បញ្ចូល",
  1079. "km-rKH": u"ភាសា & ការ​បញ្ចូល",
  1080. "ko": u"언어 및 키보드",
  1081. "lo": u"ພາສາ & ການປ້ອນຂໍ້ມູນ",
  1082. "lo-rLA": u"ພາສາ & ການປ້ອນຂໍ້ມູນ",
  1083. "lt": u"Kalba ir įvestis",
  1084. "lv": u"Valodas ievade",
  1085. "mn": u"Хэл & оруулах",
  1086. "mn-rMN": u"Хэл & оруулах",
  1087. "ms": u"Bahasa & input",
  1088. "ms-rMY": u"Bahasa & input",
  1089. "nb": u"Språk og inndata",
  1090. "ne": u"भाषा र इनपुट",
  1091. "ne-rNP": u"भाषा र इनपुट",
  1092. "nl": u"Taal en invoer",
  1093. "pl": u"Język, klawiatura, głos",
  1094. "pt": u"Idioma e entrada",
  1095. "pt-rPT": u"Idioma e entrada",
  1096. "ro": u"Limbă și introducere de text",
  1097. "ru": u"Язык и ввод",
  1098. "si": u"භාෂාව සහ ආදානය",
  1099. "si-rLK": u"භාෂාව සහ ආදානය",
  1100. "sk": u"Jazyk & vstup",
  1101. "sl": u"Jezik in vnos",
  1102. "sr": u"Језик и унос",
  1103. "sv": u"Språk och inmatning",
  1104. "sw": u"Lugha, Kibodi na Sauti",
  1105. "th": u"ภาษาและการป้อนข้อมูล",
  1106. "tl": u"Wika at input",
  1107. "tr": u"Dil ve giriş",
  1108. "uk": u"Мова та введення",
  1109. "vi": u"Ngôn ngữ & phương thức nhập",
  1110. "zh-rCN": u"语言和输入法",
  1111. "zh-rHK": u"語言與輸入裝置",
  1112. "zh-rTW": u"語言與輸入設定",
  1113. "zu": u"Ulimi & ukufakwa",
  1114. }
  1115. PHONE_LANGUAGE = {
  1116. "en": u"Language",
  1117. "af": u"Taal",
  1118. "am": u"ቋንቋ",
  1119. "ar": u"اللغة",
  1120. "az": u"Dil",
  1121. "az-rAZ": u"Dil",
  1122. "be": u"Мова",
  1123. "bg": u"Език",
  1124. "ca": u"Idioma",
  1125. "cs": u"Jazyk",
  1126. "da": u"Sprog",
  1127. "de": u"Sprache",
  1128. "el": u"Γλώσσα",
  1129. "en-rGB": u"Language",
  1130. "en-rIN": u"Language",
  1131. "es": u"Idioma",
  1132. "es-rUS": u"Idioma",
  1133. "et": u"Keel",
  1134. "et-rEE": u"Keel",
  1135. "fa": u"زبان",
  1136. "fi": u"Kieli",
  1137. "fr": u"Langue",
  1138. "fr-rCA": u"Langue",
  1139. "hi": u"भाषा",
  1140. "hr": u"Jezik",
  1141. "hu": u"Nyelv",
  1142. "hy": u"Lեզուն",
  1143. "hy-rAM": u"Lեզուն",
  1144. "in": u"Bahasa",
  1145. "it": u"Lingua",
  1146. "iw": u"שפה",
  1147. "ja": u"言語",
  1148. "ka": u"ენა",
  1149. "ka-rGE": u"ენა",
  1150. "km": u"ភាសា",
  1151. "km-rKH": u"ភាសា",
  1152. "ko": u"언어",
  1153. "lo": u"ພາສາ",
  1154. "lo-rLA": u"ພາສາ",
  1155. "lt": u"Kalba",
  1156. "lv": u"Valoda",
  1157. "mn": u"Хэл",
  1158. "mn-rMN": u"Хэл",
  1159. "ms": u"Bahasa",
  1160. "ms-rMY": u"Bahasa",
  1161. "nb": u"Språk",
  1162. "ne": u"भाषा",
  1163. "nl": u"Taal",
  1164. "pl": u"Język",
  1165. "pt": u"Idioma",
  1166. "pt-rPT": u"Idioma",
  1167. "ro": u"Limba",
  1168. "ru": u"Язык",
  1169. "si": u"භාෂාව",
  1170. "si-rLK": u"භාෂාව",
  1171. "sk": u"Jazyk",
  1172. "sl": u"Jezik",
  1173. "sr": u"Језик",
  1174. "sv": u"Språk",
  1175. "sw": u"Lugha",
  1176. "th": u"ภาษา",
  1177. "tl": u"Wika",
  1178. "tr": u"Dil",
  1179. "uk": u"Мова",
  1180. "vi": u"Ngôn ngữ",
  1181. "zh-rCN": u"语言",
  1182. "zh-rHK": u"語言",
  1183. "zh-rTW": u"語言",
  1184. "zu": u"Ulimi",
  1185. }
  1186. LANGUAGES = {
  1187. "en": u"English (United States)",
  1188. "es-rUS": u"Español (Estados Unidos)",
  1189. "af": u"Afrikaans", # Afrikaans
  1190. "af-rNA": u"Afrikaans (Namibië)", # Afrikaans (Namibia)
  1191. "af-rZA": u"Afrikaans (Suid-Afrika)", # Afrikaans (South Africa)
  1192. "agq": u"Aghem", # Aghem
  1193. "agq-rCM": u"Aghem (Kàmàlûŋ)", # Aghem (Cameroon)
  1194. "ak": u"Akan", # Akan
  1195. "ak-rGH": u"Akan (Gaana)", # Akan (Ghana)
  1196. "am": u"አማርኛ", # Amharic
  1197. "am-rET": u"አማርኛ (ኢትዮጵያ)", # Amharic (Ethiopia)
  1198. "ar": u"العربية", # Arabic
  1199. "ar_001": u"العربية (العالم)", # Arabic (World)
  1200. "ar-rAE": u"العربية (الإمارات العربية المتحدة)", # Arabic (United Arab Emirates)
  1201. "ar-rBH": u"العربية (البحرين)", # Arabic (Bahrain)
  1202. "ar-rDJ": u"العربية (جيبوتي)", # Arabic (Djibouti)
  1203. "ar-rDZ": u"العربية (الجزائر)", # Arabic (Algeria)
  1204. "ar-rEG": u"العربية (مصر)", # Arabic (Egypt)
  1205. "ar-rEH": u"العربية (الصحراء الغربية)", # Arabic (Western Sahara)
  1206. "ar-rER": u"العربية (أريتريا)", # Arabic (Eritrea)
  1207. "ar-rIL": u"العربية (إسرائيل)", # Arabic (Israel)
  1208. "ar-rIQ": u"العربية (العراق)", # Arabic (Iraq)
  1209. "ar-rJO": u"العربية (الأردن)", # Arabic (Jordan)
  1210. "ar-rKM": u"العربية (جزر القمر)", # Arabic (Comoros)
  1211. "ar-rKW": u"العربية (الكويت)", # Arabic (Kuwait)
  1212. "ar-rLB": u"العربية (لبنان)", # Arabic (Lebanon)
  1213. "ar-rLY": u"العربية (ليبيا)", # Arabic (Libya)
  1214. "ar-rMA": u"العربية (المغرب)", # Arabic (Morocco)
  1215. "ar-rMR": u"العربية (موريتانيا)", # Arabic (Mauritania)
  1216. "ar-rOM": u"العربية (عُمان)", # Arabic (Oman)
  1217. "ar-rPS": u"العربية (فلسطين)", # Arabic (Palestine)
  1218. "ar-rQA": u"العربية (قطر)", # Arabic (Qatar)
  1219. "ar-rSA": u"العربية (المملكة العربية السعودية)", # Arabic (Saudi Arabia)
  1220. "ar-rSD": u"العربية (السودان)", # Arabic (Sudan)
  1221. "ar-rSO": u"العربية (الصومال)", # Arabic (Somalia)
  1222. "ar-rSY": u"العربية (سوريا)", # Arabic (Syria)
  1223. "ar-rTD": u"العربية (تشاد)", # Arabic (Chad)
  1224. "ar-rTN": u"العربية (تونس)", # Arabic (Tunisia)
  1225. "ar-rYE": u"العربية (اليمن)", # Arabic (Yemen)
  1226. "as": u"অসমীয়া", # Assamese
  1227. "as-rIN": u"অসমীয়া (ভাৰত)", # Assamese (India)
  1228. "asa": u"Kipare", # Asu
  1229. "asa-rTZ": u"Kipare (Tadhania)", # Asu (Tanzania)
  1230. "az": u"Azərbaycanca", # Azerbaijani
  1231. "az-rCYRL": u"Азәрбајҹан (CYRL)", # Azerbaijani (CYRL)
  1232. "az-rCYRL_AZ": u"Азәрбајҹан (Азәрбајҹан,AZ)", # Azerbaijani (Azerbaijan,AZ)
  1233. "az-rLATN": u"Azərbaycanca (LATN)", # Azerbaijani (LATN)
  1234. "az-rLATN_AZ": u"Azərbaycanca (Azərbaycan,AZ)", # Azerbaijani (Azerbaijan,AZ)
  1235. "bas": u"Ɓàsàa", # Basaa
  1236. "bas-rCM": u"Ɓàsàa (Kàmɛ̀rûn)", # Basaa (Cameroon)
  1237. "be": u"беларуская", # Belarusian
  1238. "be-rBY": u"беларуская (Беларусь)", # Belarusian (Belarus)
  1239. "bem": u"Ichibemba", # Bemba
  1240. "bem-rZM": u"Ichibemba (Zambia)", # Bemba (Zambia)
  1241. "bez": u"Hibena", # Bena
  1242. "bez-rTZ": u"Hibena (Hutanzania)", # Bena (Tanzania)
  1243. "bg": u"български", # Bulgarian
  1244. "bg-rBG": u"български (България)", # Bulgarian (Bulgaria)
  1245. "bm": u"Bamanakan", # Bambara
  1246. "bm-rML": u"Bamanakan (Mali)", # Bambara (Mali)
  1247. "bn": u"বাংলা", # Bengali
  1248. "bn-rBD": u"বাংলা (বাংলাদেশ)", # Bengali (Bangladesh)
  1249. "bn-rIN": u"বাংলা (ভারত)", # Bengali (India)
  1250. "bo": u"པོད་སྐད་", # Tibetan
  1251. "bo-rCN": u"པོད་སྐད་ (རྒྱ་ནག)", # Tibetan (China)
  1252. "bo-rIN": u"པོད་སྐད་ (རྒྱ་གར་)", # Tibetan (India)
  1253. "br": u"Brezhoneg", # Breton
  1254. "br-rFR": u"Brezhoneg (Frañs)", # Breton (France)
  1255. "brx": u"बड़ो", # Bodo
  1256. "brx-rIN": u"बड़ो (भारत)", # Bodo (India)
  1257. "bs": u"Bosanski", # Bosnian
  1258. "bs-rCYRL": u"босански (CYRL)", # Bosnian (CYRL)
  1259. "bs-rCYRL_BA": u"босански (Босна и Херцеговина,BA)", # Bosnian (Bosnia and Herzegovina,BA)
  1260. "bs-rLATN": u"Bosanski (LATN)", # Bosnian (LATN)
  1261. "bs-rLATN_BA": u"Bosanski (Bosna i Hercegovina,BA)", # Bosnian (Bosnia and Herzegovina,BA)
  1262. "ca": u"Català", # Catalan
  1263. "ca-rAD": u"Català (Andorra)", # Catalan (Andorra)
  1264. "ca-rES": u"Català (Espanya)", # Catalan (Spain)
  1265. "cgg": u"Rukiga", # Chiga
  1266. "cgg-rUG": u"Rukiga (Uganda)", # Chiga (Uganda)
  1267. "chr": u"ᏣᎳᎩ", # Cherokee
  1268. "chr-rUS": u"ᏣᎳᎩ (ᎠᎹᏰᏟ)", # Cherokee (United States)
  1269. "cs": u"čeština", # Czech
  1270. "cs-rCZ": u"čeština (Česká republika)", # Czech (Czech Republic)
  1271. "cy": u"Cymraeg", # Welsh
  1272. "cy-rGB": u"Cymraeg (y Deyrnas Unedig)", # Welsh (United Kingdom)
  1273. "da": u"Dansk", # Danish
  1274. "da-rDK": u"Dansk (Danmark)", # Danish (Denmark)
  1275. "dav": u"Kitaita", # Taita
  1276. "dav-rKE": u"Kitaita (Kenya)", # Taita (Kenya)
  1277. "de": u"Deutsch", # German
  1278. "de-rAT": u"Deutsch (Österreich)", # German (Austria)
  1279. "de-rBE": u"Deutsch (Belgien)", # German (Belgium)
  1280. "de-rCH": u"Deutsch (Schweiz)", # German (Switzerland)
  1281. "de-rDE": u"Deutsch (Deutschland)", # German (Germany)
  1282. "de-rLI": u"Deutsch (Liechtenstein)", # German (Liechtenstein)
  1283. "de-rLU": u"Deutsch (Luxemburg)", # German (Luxembourg)
  1284. "dje": u"Zarmaciine", # Zarma
  1285. "dje-rNE": u"Zarmaciine (Nižer)", # Zarma (Niger)
  1286. "dua": u"Duálá", # Duala
  1287. "dua-rCM": u"Duálá (Cameroun)", # Duala (Cameroon)
  1288. "dyo": u"Joola", # Jola-Fonyi
  1289. "dyo-rSN": u"Joola (Senegal)", # Jola-Fonyi (Senegal)
  1290. "dz": u"རྫོང་ཁ", # Dzongkha
  1291. "dz-rBT": u"རྫོང་ཁ (འབྲུག)", # Dzongkha (Bhutan)
  1292. "ebu": u"Kĩembu", # Embu
  1293. "ebu-rKE": u"Kĩembu (Kenya)", # Embu (Kenya)
  1294. "ee": u"Eʋegbe", # Ewe
  1295. "ee-rGH": u"Eʋegbe (Ghana nutome)", # Ewe (Ghana)
  1296. "ee-rTG": u"Eʋegbe (Togo nutome)", # Ewe (Togo)
  1297. "el": u"Ελληνικά", # Greek
  1298. "el-rCY": u"Ελληνικά (Κύπρος)", # Greek (Cyprus)
  1299. "el-rGR": u"Ελληνικά (Ελλάδα)", # Greek (Greece)
  1300. "en": u"English", # English
  1301. "en_150": u"English (Europe)", # English (Europe)
  1302. "en-rAG": u"English (Antigua and Barbuda)", # English (Antigua and Barbuda)
  1303. "en-rAS": u"English (American Samoa)", # English (American Samoa)
  1304. "en-rAU": u"English (Australia)", # English (Australia)
  1305. "en-rBB": u"English (Barbados)", # English (Barbados)
  1306. "en-rBE": u"English (Belgium)", # English (Belgium)
  1307. "en-rBM": u"English (Bermuda)", # English (Bermuda)
  1308. "en-rBS": u"English (Bahamas)", # English (Bahamas)
  1309. "en-rBW": u"English (Botswana)", # English (Botswana)
  1310. "en-rBZ": u"English (Belize)", # English (Belize)
  1311. "en-rCA": u"English (Canada)", # English (Canada)
  1312. "en-rCM": u"English (Cameroon)", # English (Cameroon)
  1313. "en-rDM": u"English (Dominica)", # English (Dominica)
  1314. "en-rFJ": u"English (Fiji)", # English (Fiji)
  1315. "en-rFM": u"English (Micronesia)", # English (Micronesia)
  1316. "en-rGB": u"English (United Kingdom)", # English (United Kingdom)
  1317. "en-rGD": u"English (Grenada)", # English (Grenada)
  1318. "en-rGG": u"English (Guernsey)", # English (Guernsey)
  1319. "en-rGH": u"English (Ghana)", # English (Ghana)
  1320. "en-rGI": u"English (Gibraltar)", # English (Gibraltar)
  1321. "en-rGM": u"English (Gambia)", # English (Gambia)
  1322. "en-rGU": u"English (Guam)", # English (Guam)
  1323. "en-rGY": u"English (Guyana)", # English (Guyana)
  1324. "en-rHK": u"English (Hong Kong)", # English (Hong Kong)
  1325. "en-rIE": u"English (Ireland)", # English (Ireland)
  1326. "en-rIM": u"English (Isle of Man)", # English (Isle of Man)
  1327. "en-rIN": u"English (India)", # English (India)
  1328. "en-rJE": u"English (Jersey)", # English (Jersey)
  1329. "en-rJM": u"English (Jamaica)", # English (Jamaica)
  1330. "en-rKE": u"English (Kenya)", # English (Kenya)
  1331. "en-rKI": u"English (Kiribati)", # English (Kiribati)
  1332. "en-rKN": u"English (Saint Kitts and Nevis)", # English (Saint Kitts and Nevis)
  1333. "en-rKY": u"English (Cayman Islands)", # English (Cayman Islands)
  1334. "en-rLC": u"English (Saint Lucia)", # English (Saint Lucia)
  1335. "en-rLR": u"English (Liberia)", # English (Liberia)
  1336. "en-rLS": u"English (Lesotho)", # English (Lesotho)
  1337. "en-rMG": u"English (Madagascar)", # English (Madagascar)
  1338. "en-rMH": u"English (Marshall Islands)", # English (Marshall Islands)
  1339. "en-rMP": u"English (Northern Mariana Islands)", # English (Northern Mariana Islands)
  1340. "en-rMT": u"English (Malta)", # English (Malta)
  1341. "en-rMU": u"English (Mauritius)", # English (Mauritius)
  1342. "en-rMW": u"English (Malawi)", # English (Malawi)
  1343. "en-rNA": u"English (Namibia)", # English (Namibia)
  1344. "en-rNG": u"English (Nigeria)", # English (Nigeria)
  1345. "en-rNZ": u"English (New Zealand)", # English (New Zealand)
  1346. "en-rPG": u"English (Papua New Guinea)", # English (Papua New Guinea)
  1347. "en-rPH": u"English (Philippines)", # English (Philippines)
  1348. "en-rPK": u"English (Pakistan)", # English (Pakistan)
  1349. "en-rPR": u"English (Puerto Rico)", # English (Puerto Rico)
  1350. "en-rPW": u"English (Palau)", # English (Palau)
  1351. "en-rSB": u"English (Solomon Islands)", # English (Solomon Islands)
  1352. "en-rSC": u"English (Seychelles)", # English (Seychelles)
  1353. "en-rSG": u"English (Singapore)", # English (Singapore)
  1354. "en-rSL": u"English (Sierra Leone)", # English (Sierra Leone)
  1355. "en-rSS": u"English (South Sudan)", # English (South Sudan)
  1356. "en-rSZ": u"English (Swaziland)", # English (Swaziland)
  1357. "en-rTC": u"English (Turks and Caicos Islands)", # English (Turks and Caicos Islands)
  1358. "en-rTO": u"English (Tonga)", # English (Tonga)
  1359. "en-rTT": u"English (Trinidad and Tobago)", # English (Trinidad and Tobago)
  1360. "en-rTZ": u"English (Tanzania)", # English (Tanzania)
  1361. "en-rUG": u"English (Uganda)", # English (Uganda)
  1362. "en-rUM": u"English (U.S. Outlying Islands)", # English (U.S. Outlying Islands)
  1363. "en-rUS": u"English (United States)", # English (United States)
  1364. "en-rUS_POSIX": u"English (United States,Computer)", # English (United States,Computer)
  1365. "en-rVC": u"English (Saint Vincent and the Grenadines)", # English (Saint Vincent and the Grenadines)
  1366. "en-rVG": u"English (British Virgin Islands)", # English (British Virgin Islands)
  1367. "en-rVI": u"English (U.S. Virgin Islands)", # English (U.S. Virgin Islands)
  1368. "en-rVU": u"English (Vanuatu)", # English (Vanuatu)
  1369. "en-rWS": u"English (Samoa)", # English (Samoa)
  1370. "en-rZA": u"English (South Africa)", # English (South Africa)
  1371. "en-rZM": u"English (Zambia)", # English (Zambia)
  1372. "en-rZW": u"English (Zimbabwe)", # English (Zimbabwe)
  1373. "eo": u"Esperanto", # Esperanto
  1374. "es": u"Español", # Spanish
  1375. "es_419": u"Español (Latinoamérica)", # Spanish (Latin America)
  1376. "es-rAR": u"Español (Argentina)", # Spanish (Argentina)
  1377. "es-rBO": u"Español (Bolivia)", # Spanish (Bolivia)
  1378. "es-rCL": u"Español (Chile)", # Spanish (Chile)
  1379. "es-rCO": u"Español (Colombia)", # Spanish (Colombia)
  1380. "es-rCR": u"Español (Costa Rica)", # Spanish (Costa Rica)
  1381. "es-rCU": u"Español (Cuba)", # Spanish (Cuba)
  1382. "es-rDO": u"Español (República Dominicana)", # Spanish (Dominican Republic)
  1383. "es-rEA": u"Español (Ceuta y Melilla)", # Spanish (Ceuta and Melilla)
  1384. "es-rEC": u"Español (Ecuador)", # Spanish (Ecuador)
  1385. "es-rES": u"Español (España)", # Spanish (Spain)
  1386. "es-rGQ": u"Español (Guinea Ecuatorial)", # Spanish (Equatorial Guinea)
  1387. "es-rGT": u"Español (Guatemala)", # Spanish (Guatemala)
  1388. "es-rHN": u"Español (Honduras)", # Spanish (Honduras)
  1389. "es-rIC": u"Español (Islas Canarias)", # Spanish (Canary Islands)
  1390. "es-rMX": u"Español (México)", # Spanish (Mexico)
  1391. "es-rNI": u"Español (Nicaragua)", # Spanish (Nicaragua)
  1392. "es-rPA": u"Español (Panamá)", # Spanish (Panama)
  1393. "es-rPE": u"Español (Perú)", # Spanish (Peru)
  1394. "es-rPH": u"Español (Filipinas)", # Spanish (Philippines)
  1395. "es-rPR": u"Español (Puerto Rico)", # Spanish (Puerto Rico)
  1396. "es-rPY": u"Español (Paraguay)", # Spanish (Paraguay)
  1397. "es-rSV": u"Español (El Salvador)", # Spanish (El Salvador)
  1398. "es-rUS": u"Español (Estados Unidos)", # Spanish (United States)
  1399. "es-rUY": u"Español (Uruguay)", # Spanish (Uruguay)
  1400. "es-rVE": u"Español (Venezuela)", # Spanish (Venezuela)
  1401. "et": u"Eesti", # Estonian
  1402. "et-rEE": u"Eesti (Eesti)", # Estonian (Estonia)
  1403. "eu": u"Euskara", # Basque
  1404. "eu-rES": u"Euskara (Espainia)", # Basque (Spain)
  1405. "ewo": u"Ewondo", # Ewondo
  1406. "ewo-rCM": u"Ewondo (Kamərún)", # Ewondo (Cameroon)
  1407. "fa": u"فارسی", # Persian
  1408. "fa-rAF": u"دری (افغانستان)", # Persian (Afghanistan)
  1409. "fa-rIR": u"فارسی (ایران)", # Persian (Iran)
  1410. "ff": u"Pulaar", # Fulah
  1411. "ff-rSN": u"Pulaar (Senegaal)", # Fulah (Senegal)
  1412. "fi": u"Suomi", # Finnish
  1413. "fi-rFI": u"Suomi (Suomi)", # Finnish (Finland)
  1414. "fil": u"Filipino", # Filipino
  1415. "fil-rPH": u"Filipino (Pilipinas)", # Filipino (Philippines)
  1416. "fo": u"Føroyskt", # Faroese
  1417. "fo-rFO": u"Føroyskt (Føroyar)", # Faroese (Faroe Islands)
  1418. "fr": u"Français", # French
  1419. "fr-rBE": u"Français (Belgique)", # French (Belgium)
  1420. "fr-rBF": u"Français (Burkina Faso)", # French (Burkina Faso)
  1421. "fr-rBI": u"Français (Burundi)", # French (Burundi)
  1422. "fr-rBJ": u"Français (Bénin)", # French (Benin)
  1423. "fr-rBL": u"Français (Saint-Barthélémy)", # French (Saint Barthélemy)
  1424. "fr-rCA": u"Français (Canada)", # French (Canada)
  1425. "fr-rCD": u"Français (République démocratique du Congo)", # French (Congo [DRC])
  1426. "fr-rCF": u"Français (République centrafricaine)", # French (Central African Republic)
  1427. "fr-rCG": u"Français (Congo-Brazzaville)", # French (Congo [Republic])
  1428. "fr-rCH": u"Français (Suisse)", # French (Switzerland)
  1429. "fr-rCI": u"Français (Côte d’Ivoire)", # French (Côte d’Ivoire)
  1430. "fr-rCM": u"Français (Cameroun)", # French (Cameroon)
  1431. "fr-rDJ": u"Français (Djibouti)", # French (Djibouti)
  1432. "fr-rDZ": u"Français (Algérie)", # French (Algeria)
  1433. "fr-rFR": u"Français (France)", # French (France)
  1434. "fr-rGA": u"Français (Gabon)", # French (Gabon)
  1435. "fr-rGF": u"Français (Guyane française)", # French (French Guiana)
  1436. "fr-rGN": u"Français (Guinée)", # French (Guinea)
  1437. "fr-rGP": u"Français (Guadeloupe)", # French (Guadeloupe)
  1438. "fr-rGQ": u"Français (Guinée équatoriale)", # French (Equatorial Guinea)
  1439. "fr-rHT": u"Français (Haïti)", # French (Haiti)
  1440. "fr-rKM": u"Français (Comores)", # French (Comoros)
  1441. "fr-rLU": u"Français (Luxembourg)", # French (Luxembourg)
  1442. "fr-rMA": u"Français (Maroc)", # French (Morocco)
  1443. "fr-rMC": u"Français (Monaco)", # French (Monaco)
  1444. "fr-rMF": u"Français (Saint-Martin [partie française])", # French (Saint Martin)
  1445. "fr-rMG": u"Français (Madagascar)", # French (Madagascar)
  1446. "fr-rML": u"Français (Mali)", # French (Mali)
  1447. "fr-rMQ": u"Français (Martinique)", # French (Martinique)
  1448. "fr-rMR": u"Français (Mauritanie)", # French (Mauritania)
  1449. "fr-rMU": u"Français (Maurice)", # French (Mauritius)
  1450. "fr-rNC": u"Français (Nouvelle-Calédonie)", # French (New Caledonia)
  1451. "fr-rNE": u"Français (Niger)", # French (Niger)
  1452. "fr-rPF": u"Français (Polynésie française)", # French (French Polynesia)
  1453. "fr-rRE": u"Français (Réunion)", # French (Réunion)
  1454. "fr-rRW": u"Français (Rwanda)", # French (Rwanda)
  1455. "fr-rSC": u"Français (Seychelles)", # French (Seychelles)
  1456. "fr-rSN": u"Français (Sénégal)", # French (Senegal)
  1457. "fr-rSY": u"Français (Syrie)", # French (Syria)
  1458. "fr-rTD": u"Français (Tchad)", # French (Chad)
  1459. "fr-rTG": u"Français (Togo)", # French (Togo)
  1460. "fr-rTN": u"Français (Tunisie)", # French (Tunisia)
  1461. "fr-rVU": u"Français (Vanuatu)", # French (Vanuatu)
  1462. "fr-rYT": u"Français (Mayotte)", # French (Mayotte)
  1463. "ga": u"Gaeilge", # Irish
  1464. "ga-rIE": u"Gaeilge (Éire)", # Irish (Ireland)
  1465. "gl": u"Galego", # Galician
  1466. "gl-rES": u"Galego (España)", # Galician (Spain)
  1467. "gsw": u"Schwiizertüütsch", # Swiss German
  1468. "gsw-rCH": u"Schwiizertüütsch (Schwiiz)", # Swiss German (Switzerland)
  1469. "gu": u"ગુજરાતી", # Gujarati
  1470. "gu-rIN": u"ગુજરાતી (ભારત)", # Gujarati (India)
  1471. "guz": u"Ekegusii", # Gusii
  1472. "guz-rKE": u"Ekegusii (Kenya)", # Gusii (Kenya)
  1473. "gv": u"Gaelg", # Manx
  1474. "gv-rGB": u"Gaelg (Rywvaneth Unys)", # Manx (United Kingdom)
  1475. "ha": u"Hausa", # Hausa
  1476. "ha-rLATN": u"Hausa (LATN)", # Hausa (LATN)
  1477. "ha-rLATN_GH": u"Hausa (Gana,GH)", # Hausa (Ghana,GH)
  1478. "ha-rLATN_NE": u"Hausa (Nijar,NE)", # Hausa (Niger,NE)
  1479. "ha-rLATN_NG": u"Hausa (Najeriya,NG)", # Hausa (Nigeria,NG)
  1480. "haw": u"ʻŌlelo Hawaiʻi", # Hawaiian
  1481. "haw-rUS": u"ʻŌlelo Hawaiʻi (ʻAmelika Hui Pū ʻIa)", # Hawaiian (United States)
  1482. "iw": u"עברית", # Hebrew
  1483. "iw-rIL": u"עברית (ישראל)", # Hebrew (Israel)
  1484. "hi": u"हिन्दी", # Hindi
  1485. "hi-rIN": u"हिन्दी (भारत)", # Hindi (India)
  1486. "hr": u"Hrvatski", # Croatian
  1487. "hr-rBA": u"Hrvatski (Bosna i Hercegovina)", # Croatian (Bosnia and Herzegovina)
  1488. "hr-rHR": u"Hrvatski (Hrvatska)", # Croatian (Croatia)
  1489. "hu": u"Magyar", # Hungarian
  1490. "hu-rHU": u"Magyar (Magyarország)", # Hungarian (Hungary)
  1491. "hy": u"հայերեն", # Armenian
  1492. "hy-rAM": u"հայերեն (Հայաստան)", # Armenian (Armenia)
  1493. "in": u"Bahasa Indonesia", # Indonesian
  1494. "in-rID": u"Bahasa Indonesia (Indonesia)", # Indonesian (Indonesia)
  1495. "ig": u"Igbo", # Igbo
  1496. "ig-rNG": u"Igbo (Nigeria)", # Igbo (Nigeria)
  1497. "ii": u"ꆈꌠꉙ", # Sichuan Yi
  1498. "ii-rCN": u"ꆈꌠꉙ (ꍏꇩ)", # Sichuan Yi (China)
  1499. "is": u"íslenska", # Icelandic
  1500. "is-rIS": u"íslenska (Ísland)", # Icelandic (Iceland)
  1501. "it": u"Italiano", # Italian
  1502. "it-rCH": u"Italiano (Svizzera)", # Italian (Switzerland)
  1503. "it-rIT": u"Italiano (Italia)", # Italian (Italy)
  1504. "it-rSM": u"Italiano (San Marino)", # Italian (San Marino)
  1505. "ja": u"日本語", # Japanese
  1506. "ja-rJP": u"日本語 (日本)", # Japanese (Japan)
  1507. "jgo": u"Ndaꞌa", # Ngomba
  1508. "jgo-rCM": u"Ndaꞌa (Kamɛlûn)", # Ngomba (Cameroon)
  1509. "jmc": u"Kimachame", # Machame
  1510. "jmc-rTZ": u"Kimachame (Tanzania)", # Machame (Tanzania)
  1511. "ka": u"ქართული", # Georgian
  1512. "ka-rGE": u"ქართული (საქართველო)", # Georgian (Georgia)
  1513. "kab": u"Taqbaylit", # Kabyle
  1514. "kab-rDZ": u"Taqbaylit (Lezzayer)", # Kabyle (Algeria)
  1515. "kam": u"Kikamba", # Kamba
  1516. "kam-rKE": u"Kikamba (Kenya)", # Kamba (Kenya)
  1517. "kde": u"Chimakonde", # Makonde
  1518. "kde-rTZ": u"Chimakonde (Tanzania)", # Makonde (Tanzania)
  1519. "kea": u"Kabuverdianu", # Kabuverdianu
  1520. "kea-rCV": u"Kabuverdianu (Kabu Verdi)", # Kabuverdianu (Cape Verde)
  1521. "khq": u"Koyra ciini", # Koyra Chiini
  1522. "khq-rML": u"Koyra ciini (Maali)", # Koyra Chiini (Mali)
  1523. "ki": u"Gikuyu", # Kikuyu
  1524. "ki-rKE": u"Gikuyu (Kenya)", # Kikuyu (Kenya)
  1525. "kk": u"қазақ тілі", # Kazakh
  1526. "kk-rCYRL": u"қазақ тілі (CYRL)", # Kazakh (CYRL)
  1527. "kk-rCYRL_KZ": u"қазақ тілі (Қазақстан,KZ)", # Kazakh (Kazakhstan,KZ)
  1528. "kl": u"Kalaallisut", # Kalaallisut
  1529. "kl-rGL": u"Kalaallisut (Kalaallit Nunaat)", # Kalaallisut (Greenland)
  1530. "kln": u"Kalenjin", # Kalenjin
  1531. "kln-rKE": u"Kalenjin (Emetab Kenya)", # Kalenjin (Kenya)
  1532. "km": u"ខ្មែរ", # Khmer
  1533. "km-rKH": u"ខ្មែរ (កម្ពុជា)", # Khmer (Cambodia)
  1534. "kn": u"ಕನ್ನಡ", # Kannada
  1535. "kn-rIN": u"ಕನ್ನಡ (ಭಾರತ)", # Kannada (India)
  1536. "ko": u"한국어", # Korean
  1537. "ko-rKP": u"한국어 (조선 민주주의 인민 공화국)", # Korean (North Korea)
  1538. "ko-rKR": u"한국어 (대한민국)", # Korean (South Korea)
  1539. "kok": u"कोंकणी", # Konkani
  1540. "kok-rIN": u"कोंकणी (भारत)", # Konkani (India)
  1541. "ks": u"کٲشُر", # Kashmiri
  1542. "ks-rARAB": u"کٲشُر (ARAB)", # Kashmiri (ARAB)
  1543. "ks-rARAB_IN": u"کٲشُر (ہِنٛدوستان,IN)", # Kashmiri (India,IN)
  1544. "ksb": u"Kishambaa", # Shambala
  1545. "ksb-rTZ": u"Kishambaa (Tanzania)", # Shambala (Tanzania)
  1546. "ksf": u"Rikpa", # Bafia
  1547. "ksf-rCM": u"Rikpa (kamɛrún)", # Bafia (Cameroon)
  1548. "kw": u"Kernewek", # Cornish
  1549. "kw-rGB": u"Kernewek (Rywvaneth Unys)", # Cornish (United Kingdom)
  1550. "lag": u"Kɨlaangi", # Langi
  1551. "lag-rTZ": u"Kɨlaangi (Taansanía)", # Langi (Tanzania)
  1552. "lg": u"Luganda", # Ganda
  1553. "lg-rUG": u"Luganda (Yuganda)", # Ganda (Uganda)
  1554. "ln": u"Lingála", # Lingala
  1555. "ln-rAO": u"Lingála (Angóla)", # Lingala (Angola)
  1556. "ln-rCD": u"Lingála (Repibiki demokratiki ya Kongó)", # Lingala (Congo [DRC])
  1557. "ln-rCF": u"Lingála (Repibiki ya Afríka ya Káti)", # Lingala (Central African Republic)
  1558. "ln-rCG": u"Lingála (Kongo)", # Lingala (Congo [Republic])
  1559. "lo": u"ລາວ", # Lao
  1560. "lo-rLA": u"ລາວ (ສ.ປ.ປ ລາວ)", # Lao (Laos)
  1561. "lt": u"Lietuvių", # Lithuanian
  1562. "lt-rLT": u"Lietuvių (Lietuva)", # Lithuanian (Lithuania)
  1563. "lu": u"Tshiluba", # Luba-Katanga
  1564. "lu-rCD": u"Tshiluba (Ditunga wa Kongu)", # Luba-Katanga (Congo [DRC])
  1565. "luo": u"Dholuo", # Luo
  1566. "luo-rKE": u"Dholuo (Kenya)", # Luo (Kenya)
  1567. "luy": u"Luluhia", # Luyia
  1568. "luy-rKE": u"Luluhia (Kenya)", # Luyia (Kenya)
  1569. "lv": u"Latviešu", # Latvian
  1570. "lv-rLV": u"Latviešu (Latvija)", # Latvian (Latvia)
  1571. "mas": u"Maa", # Masai
  1572. "mas-rKE": u"Maa (Kenya)", # Masai (Kenya)
  1573. "mas-rTZ": u"Maa (Tansania)", # Masai (Tanzania)
  1574. "mer": u"Kĩmĩrũ", # Meru
  1575. "mer-rKE": u"Kĩmĩrũ (Kenya)", # Meru (Kenya)
  1576. "mfe": u"Kreol morisien", # Morisyen
  1577. "mfe-rMU": u"Kreol morisien (Moris)", # Morisyen (Mauritius)
  1578. "mg": u"Malagasy", # Malagasy
  1579. "mg-rMG": u"Malagasy (Madagasikara)", # Malagasy (Madagascar)
  1580. "mgh": u"Makua", # Makhuwa-Meetto
  1581. "mgh-rMZ": u"Makua (Umozambiki)", # Makhuwa-Meetto (Mozambique)
  1582. "mgo": u"Metaʼ", # Meta'
  1583. "mgo-rCM": u"Metaʼ (Kamalun)", # Meta' (Cameroon)
  1584. "mk": u"македонски", # Macedonian
  1585. "mk-rMK": u"македонски (Македонија)", # Macedonian (Macedonia [FYROM])
  1586. "ml": u"മലയാളം", # Malayalam
  1587. "ml-rIN": u"മലയാളം (ഇന്ത്യ)", # Malayalam (India)
  1588. "mn": u"монгол", # Mongolian
  1589. "mn-rCYRL": u"монгол (CYRL)", # Mongolian (CYRL)
  1590. "mn-rCYRL_MN": u"монгол (Монгол,MN)", # Mongolian (Mongolia,MN)
  1591. "mr": u"मराठी", # Marathi
  1592. "mr-rIN": u"मराठी (भारत)", # Marathi (India)
  1593. "ms": u"Bahasa Melayu", # Malay
  1594. "ms-rLATN": u"Bahasa Melayu (LATN)", # Malay (LATN)
  1595. "ms-rLATN_BN": u"Bahasa Melayu (Brunei,BN)", # Malay (Brunei,BN)
  1596. "ms-rLATN_MY": u"Bahasa Melayu (Malaysia,MY)", # Malay (Malaysia,MY)
  1597. "ms-rLATN_SG": u"Bahasa Melayu (Singapura,SG)", # Malay (Singapore,SG)
  1598. "mt": u"Malti", # Maltese
  1599. "mt-rMT": u"Malti (Malta)", # Maltese (Malta)
  1600. "mua": u"MUNDAŊ", # Mundang
  1601. "mua-rCM": u"MUNDAŊ (kameruŋ)", # Mundang (Cameroon)
  1602. "my": u"ဗမာ", # Burmese
  1603. "my-rMM": u"ဗမာ (မြန်မာ)", # Burmese (Myanmar [Burma])
  1604. "naq": u"Khoekhoegowab", # Nama
  1605. "naq-rNA": u"Khoekhoegowab (Namibiab)", # Nama (Namibia)
  1606. "nb": u"Norsk bokmål", # Norwegian Bokmål
  1607. "nb-rNO": u"Norsk bokmål (Norge)", # Norwegian Bokmål (Norway)
  1608. "nd": u"IsiNdebele", # North Ndebele
  1609. "nd-rZW": u"IsiNdebele (Zimbabwe)", # North Ndebele (Zimbabwe)
  1610. "ne": u"नेपाली", # Nepali
  1611. "ne-rIN": u"नेपाली (भारत)", # Nepali (India)
  1612. "ne-rNP": u"नेपाली (नेपाल)", # Nepali (Nepal)
  1613. "nl": u"Nederlands", # Dutch
  1614. "nl-rAW": u"Nederlands (Aruba)", # Dutch (Aruba)
  1615. "nl-rBE": u"Nederlands (België)", # Dutch (Belgium)
  1616. "nl-rCW": u"Nederlands (Curaçao)", # Dutch (Curaçao)
  1617. "nl-rNL": u"Nederlands (Nederland)", # Dutch (Netherlands)
  1618. "nl-rSR": u"Nederlands (Suriname)", # Dutch (Suriname)
  1619. "nl-rSX": u"Nederlands (Sint-Maarten)", # Dutch (Sint Maarten)
  1620. "nmg": u"Nmg", # Kwasio
  1621. "nmg-rCM": u"Nmg (Kamerun)", # Kwasio (Cameroon)
  1622. "nn": u"Nynorsk", # Norwegian Nynorsk
  1623. "nn-rNO": u"Nynorsk (Noreg)", # Norwegian Nynorsk (Norway)
  1624. "nus": u"Thok Nath", # Nuer
  1625. "nus-rSD": u"Thok Nath (Sudan)", # Nuer (Sudan)
  1626. "nyn": u"Runyankore", # Nyankole
  1627. "nyn-rUG": u"Runyankore (Uganda)", # Nyankole (Uganda)
  1628. "om": u"Oromoo", # Oromo
  1629. "om-rET": u"Oromoo (Itoophiyaa)", # Oromo (Ethiopia)
  1630. "om-rKE": u"Oromoo (Keeniyaa)", # Oromo (Kenya)
  1631. "or": u"ଓଡ଼ିଆ", # Oriya
  1632. "or-rIN": u"ଓଡ଼ିଆ (ଭାରତ)", # Oriya (India)
  1633. "pa": u"ਪੰਜਾਬੀ", # Punjabi
  1634. "pa-rARAB": u"پنجاب (ARAB)", # Punjabi (ARAB)
  1635. "pa-rARAB_PK": u"پنجاب (پکستان,PK)", # Punjabi (Pakistan,PK)
  1636. "pa-rGURU": u"ਪੰਜਾਬੀ (GURU)", # Punjabi (GURU)
  1637. "pa-rGURU_IN": u"ਪੰਜਾਬੀ (ਭਾਰਤ,IN)", # Punjabi (India,IN)
  1638. "pl": u"Polski", # Polish
  1639. "pl-rPL": u"Polski (Polska)", # Polish (Poland)
  1640. "ps": u"پښتو", # Pashto
  1641. "ps-rAF": u"پښتو (افغانستان)", # Pashto (Afghanistan)
  1642. "pt": u"Português", # Portuguese
  1643. "pt-rAO": u"Português (Angola)", # Portuguese (Angola)
  1644. "pt-rBR": u"Português (Brasil)", # Portuguese (Brazil)
  1645. "pt-rCV": u"Português (Cabo Verde)", # Portuguese (Cape Verde)
  1646. "pt-rGW": u"Português (Guiné Bissau)", # Portuguese (Guinea-Bissau)
  1647. "pt-rMO": u"Português (Macau)", # Portuguese (Macau)
  1648. "pt-rMZ": u"Português (Moçambique)", # Portuguese (Mozambique)
  1649. "pt-rPT": u"Português (Portugal)", # Portuguese (Portugal)
  1650. "pt-rST": u"Português (São Tomé e Príncipe)", # Portuguese (São Tomé and Príncipe)
  1651. "pt-rTL": u"Português (Timor-Leste)", # Portuguese (Timor-Leste)
  1652. "rm": u"Rumantsch", # Romansh
  1653. "rm-rCH": u"Rumantsch (Svizra)", # Romansh (Switzerland)
  1654. "rn": u"Ikirundi", # Rundi
  1655. "rn-rBI": u"Ikirundi (Uburundi)", # Rundi (Burundi)
  1656. "ro": u"Română", # Romanian
  1657. "ro-rMD": u"Română (Republica Moldova)", # Romanian (Moldova)
  1658. "ro-rRO": u"Română (România)", # Romanian (Romania)
  1659. "rof": u"Kihorombo", # Rombo
  1660. "rof-rTZ": u"Kihorombo (Tanzania)", # Rombo (Tanzania)
  1661. "ru": u"русский", # Russian
  1662. "ru-rBY": u"русский (Беларусь)", # Russian (Belarus)
  1663. "ru-rKG": u"русский (Киргизия)", # Russian (Kyrgyzstan)
  1664. "ru-rKZ": u"русский (Казахстан)", # Russian (Kazakhstan)
  1665. "ru-rMD": u"русский (Молдова)", # Russian (Moldova)
  1666. "ru-rRU": u"русский (Россия)", # Russian (Russia)
  1667. "ru-rUA": u"русский (Украина)", # Russian (Ukraine)
  1668. "rw": u"Kinyarwanda", # Kinyarwanda
  1669. "rw-rRW": u"Kinyarwanda (Rwanda)", # Kinyarwanda (Rwanda)
  1670. "rwk": u"Kiruwa", # Rwa
  1671. "rwk-rTZ": u"Kiruwa (Tanzania)", # Rwa (Tanzania)
  1672. "saq": u"Kisampur", # Samburu
  1673. "saq-rKE": u"Kisampur (Kenya)", # Samburu (Kenya)
  1674. "sbp": u"Ishisangu", # Sangu
  1675. "sbp-rTZ": u"Ishisangu (Tansaniya)", # Sangu (Tanzania)
  1676. "seh": u"Sena", # Sena
  1677. "seh-rMZ": u"Sena (Moçambique)", # Sena (Mozambique)
  1678. "ses": u"Koyraboro senni", # Koyraboro Senni
  1679. "ses-rML": u"Koyraboro senni (Maali)", # Koyraboro Senni (Mali)
  1680. "sg": u"Sängö", # Sango
  1681. "sg-rCF": u"Sängö (Ködörösêse tî Bêafrîka)", # Sango (Central African Republic)
  1682. "shi": u"ⵜⴰⵎⴰⵣⵉⵖⵜ", # Tachelhit
  1683. "shi-rLATN": u"Tamazight (LATN)", # Tachelhit (LATN)
  1684. "shi-rLATN_MA": u"Tamazight (lmɣrib,MA)", # Tachelhit (Morocco,MA)
  1685. "shi-rTFNG": u"ⵜⴰⵎⴰⵣⵉⵖⵜ (TFNG)", # Tachelhit (TFNG)
  1686. "shi-rTFNG_MA": u"ⵜⴰⵎⴰⵣⵉⵖⵜ (ⵍⵎⵖⵔⵉⴱ,MA)", # Tachelhit (Morocco,MA)
  1687. "si": u"සිංහල", # Sinhala
  1688. "si-rLK": u"සිංහල (ශ්‍රී ලංකාව)", # Sinhala (Sri Lanka)
  1689. "sk": u"Slovenčina", # Slovak
  1690. "sk-rSK": u"Slovenčina (Slovensko)", # Slovak (Slovakia)
  1691. "sl": u"Slovenščina", # Slovenian
  1692. "sl-rSI": u"Slovenščina (Slovenija)", # Slovenian (Slovenia)
  1693. "sn": u"ChiShona", # Shona
  1694. "sn-rZW": u"ChiShona (Zimbabwe)", # Shona (Zimbabwe)
  1695. "so": u"Soomaali", # Somali
  1696. "so-rDJ": u"Soomaali (Jabuuti)", # Somali (Djibouti)
  1697. "so-rET": u"Soomaali (Itoobiya)", # Somali (Ethiopia)
  1698. "so-rKE": u"Soomaali (Kiiniya)", # Somali (Kenya)
  1699. "so-rSO": u"Soomaali (Soomaaliya)", # Somali (Somalia)
  1700. "sq": u"Shqip", # Albanian
  1701. "sq-rAL": u"Shqip (Shqipëria)", # Albanian (Albania)
  1702. "sq-rMK": u"Shqip (Maqedoni)", # Albanian (Macedonia [FYROM])
  1703. "sr": u"Српски", # Serbian
  1704. "sr-rCYRL": u"Српски (CYRL)", # Serbian (CYRL)
  1705. "sr-rCYRL_BA": u"Српски (Босна и Херцеговина,BA)", # Serbian (Bosnia and Herzegovina,BA)
  1706. "sr-rCYRL_ME": u"Српски (Црна Гора,ME)", # Serbian (Montenegro,ME)
  1707. "sr-rCYRL_RS": u"Српски (Србија,RS)", # Serbian (Serbia,RS)
  1708. "sr-rLATN": u"Srpski (LATN)", # Serbian (LATN)
  1709. "sr-rLATN_BA": u"Srpski (Bosna i Hercegovina,BA)", # Serbian (Bosnia and Herzegovina,BA)
  1710. "sr-rLATN_ME": u"Srpski (Crna Gora,ME)", # Serbian (Montenegro,ME)
  1711. "sr-rLATN_RS": u"Srpski (Srbija,RS)", # Serbian (Serbia,RS)
  1712. "sv": u"Svenska", # Swedish
  1713. "sv-rAX": u"Svenska (Åland)", # Swedish (Åland Islands)
  1714. "sv-rFI": u"Svenska (Finland)", # Swedish (Finland)
  1715. "sv-rSE": u"Svenska (Sverige)", # Swedish (Sweden)
  1716. "sw": u"Kiswahili", # Swahili
  1717. "sw-rKE": u"Kiswahili (Kenya)", # Swahili (Kenya)
  1718. "sw-rTZ": u"Kiswahili (Tanzania)", # Swahili (Tanzania)
  1719. "sw-rUG": u"Kiswahili (Uganda)", # Swahili (Uganda)
  1720. "swc": u"Kiswahili ya Kongo", # Congo Swahili
  1721. "swc-rCD": u"Kiswahili ya Kongo (Jamhuri ya Kidemokrasia ya Kongo)", # Congo Swahili (Congo [DRC])
  1722. "ta": u"தமிழ்", # Tamil
  1723. "ta-rIN": u"தமிழ் (இந்தியா)", # Tamil (India)
  1724. "ta-rLK": u"தமிழ் (இலங்கை)", # Tamil (Sri Lanka)
  1725. "ta-rMY": u"தமிழ் (மலேஷியா)", # Tamil (Malaysia)
  1726. "ta-rSG": u"தமிழ் (சிங்கப்பூர்)", # Tamil (Singapore)
  1727. "te": u"తెలుగు", # Telugu
  1728. "te-rIN": u"తెలుగు (భారత దేశం)", # Telugu (India)
  1729. "teo": u"Kiteso", # Teso
  1730. "teo-rKE": u"Kiteso (Kenia)", # Teso (Kenya)
  1731. "teo-rUG": u"Kiteso (Uganda)", # Teso (Uganda)
  1732. "th": u"ไทย", # Thai
  1733. "th-rTH": u"ไทย (ไทย)", # Thai (Thailand)
  1734. "ti": u"ትግርኛ", # Tigrinya
  1735. "ti-rER": u"ትግርኛ (ER)", # Tigrinya (Eritrea)
  1736. "ti-rET": u"ትግርኛ (ET)", # Tigrinya (Ethiopia)
  1737. "to": u"Lea fakatonga", # Tongan
  1738. "to-rTO": u"Lea fakatonga (Tonga)", # Tongan (Tonga)
  1739. "tr": u"Türkçe", # Turkish
  1740. "tr-rCY": u"Türkçe (Güney Kıbrıs Rum Kesimi)", # Turkish (Cyprus)
  1741. "tr-rTR": u"Türkçe (Türkiye)", # Turkish (Turkey)
  1742. "twq": u"Tasawaq senni", # Tasawaq
  1743. "twq-rNE": u"Tasawaq senni (Nižer)", # Tasawaq (Niger)
  1744. "tzm": u"Tamaziɣt", # Central Atlas Tamazight
  1745. "tzm-rLATN": u"Tamaziɣt (LATN)", # Central Atlas Tamazight (LATN)
  1746. "tzm-rLATN_MA": u"Tamaziɣt (Meṛṛuk,MA)", # Central Atlas Tamazight (Morocco,MA)
  1747. "uk": u"українська", # Ukrainian
  1748. "uk-rUA": u"українська (Україна)", # Ukrainian (Ukraine)
  1749. "ur": u"اردو", # Urdu
  1750. "ur-rIN": u"اردو (بھارت)", # Urdu (India)
  1751. "ur-rPK": u"اردو (پاکستان)", # Urdu (Pakistan)
  1752. "uz": u"Ўзбек", # Uzbek
  1753. "uz-rARAB": u"اوزبیک (ARAB)", # Uzbek (ARAB)
  1754. "uz-rARAB_AF": u"اوزبیک (افغانستان,AF)", # Uzbek (Afghanistan,AF)
  1755. "uz-rCYRL": u"Ўзбек (CYRL)", # Uzbek (CYRL)
  1756. "uz-rCYRL_UZ": u"Ўзбек (Ўзбекистон,UZ)", # Uzbek (Uzbekistan,UZ)
  1757. "uz-rLATN": u"Oʻzbekcha (LATN)", # Uzbek (LATN)
  1758. "uz-rLATN_UZ": u"Oʻzbekcha (Oʻzbekiston,UZ)", # Uzbek (Uzbekistan,UZ)
  1759. "vai": u"ꕙꔤ", # Vai
  1760. "vai-rLATN": u"Vai (LATN)", # Vai (LATN)
  1761. "vai-rLATN_LR": u"Vai (Laibhiya,LR)", # Vai (Liberia,LR)
  1762. "vai-rVAII": u"ꕙꔤ (VAII)", # Vai (VAII)
  1763. "vai-rVAII_LR": u"ꕙꔤ (ꕞꔤꔫꕩ,LR)", # Vai (Liberia,LR)
  1764. "vi": u"Tiếng Việt", # Vietnamese
  1765. "vi-rVN": u"Tiếng Việt (Việt Nam)", # Vietnamese (Vietnam)
  1766. "vun": u"Kyivunjo", # Vunjo
  1767. "vun-rTZ": u"Kyivunjo (Tanzania)", # Vunjo (Tanzania)
  1768. "xog": u"Olusoga", # Soga
  1769. "xog-rUG": u"Olusoga (Yuganda)", # Soga (Uganda)
  1770. "yav": u"Nuasue", # Yangben
  1771. "yav-rCM": u"Nuasue (Kemelún)", # Yangben (Cameroon)
  1772. "yo": u"Èdè Yorùbá", # Yoruba
  1773. "yo-rNG": u"Èdè Yorùbá (Orílẹ́ède Nàìjíríà)", # Yoruba (Nigeria)
  1774. # This was the obtained from Locale, but it seems it's different in Settings
  1775. #"zh": u"中文", # Chinese
  1776. "zh": u"中文 (简体)", # Chinese
  1777. "zh-rHANS": u"中文 (HANS)", # Chinese (HANS)
  1778. "zh-rHANS_CN": u"中文 (中国,CN)", # Chinese (China,CN)
  1779. "zh-rHANS_HK": u"中文 (香港,HK)", # Chinese (Hong Kong,HK)
  1780. "zh-rHANS_MO": u"中文 (澳门,MO)", # Chinese (Macau,MO)
  1781. "zh-rHANS_SG": u"中文 (新加坡,SG)", # Chinese (Singapore,SG)
  1782. "zh-rHANT": u"中文 (HANT)", # Chinese (HANT)
  1783. "zh-rHANT_HK": u"中文 (香港,HK)", # Chinese (Hong Kong,HK)
  1784. "zh-rHANT_MO": u"中文 (澳門,MO)", # Chinese (Macau,MO)
  1785. "zh-rHANT_TW": u"中文 (台灣,TW)", # Chinese (Taiwan,TW)
  1786. "zu": u"IsiZulu", # Zulu
  1787. "zu-rZA": u"IsiZulu (iNingizimu Afrika)", # Zulu (South Africa)
  1788. }
  1789. if not languageTo in LANGUAGES.keys():
  1790. raise RuntimeError("%s is not a supported language by AndroidViewClient" % languageTo)
  1791. self.openQuickSettingsSettings()
  1792. view = None
  1793. currentLanguage = None
  1794. ATTEMPTS = 10
  1795. if self.vc.getSdkVersion() >= 20:
  1796. for _ in range(ATTEMPTS):
  1797. com_android_settings___id_dashboard = self.vc.findViewByIdOrRaise("com.android.settings:id/dashboard")
  1798. for k, v in LANGUAGE_SETTINGS.iteritems():
  1799. view = self.vc.findViewWithText(v, root=com_android_settings___id_dashboard)
  1800. if view:
  1801. currentLanguage = k
  1802. break
  1803. if view:
  1804. break
  1805. com_android_settings___id_dashboard.uiScrollable.flingForward()
  1806. self.vc.sleep(1)
  1807. self.vc.dump(-1)
  1808. if view is None:
  1809. raise ViewNotFoundException("text", "'Language & input' (any language)", "ROOT")
  1810. view.touch()
  1811. self.vc.sleep(1)
  1812. self.vc.dump(-1)
  1813. self.vc.findViewWithTextOrRaise(PHONE_LANGUAGE[currentLanguage]).touch()
  1814. self.vc.sleep(1)
  1815. self.vc.dump(-1)
  1816. else:
  1817. for _ in range(ATTEMPTS):
  1818. android___id_list = self.vc.findViewByIdOrRaise("android:id/list")
  1819. for k, v in LANGUAGE_SETTINGS.iteritems():
  1820. view = self.vc.findViewWithText(v, root=android___id_list)
  1821. if view:
  1822. currentLanguage = k
  1823. break
  1824. if view:
  1825. break
  1826. android___id_list.uiScrollable.flingForward()
  1827. self.vc.sleep(1)
  1828. self.vc.dump(-1)
  1829. if view is None:
  1830. raise ViewNotFoundException("text", "'Language & input' (any language)", "ROOT")
  1831. view.touch()
  1832. self.vc.sleep(1)
  1833. self.vc.dump(-1)
  1834. self.vc.findViewWithTextOrRaise(PHONE_LANGUAGE[currentLanguage]).touch()
  1835. self.vc.sleep(1)
  1836. self.vc.dump(-1)
  1837. android___id_list = self.vc.findViewByIdOrRaise("android:id/list")
  1838. android___id_list.uiScrollable.setViewClient(self.vc)
  1839. view = android___id_list.uiScrollable.scrollTextIntoView(LANGUAGES[languageTo])
  1840. if view is not None:
  1841. view.touch()
  1842. else:
  1843. #raise RuntimeError(u"Couldn't change language to %s (%s)" % (LANGUAGES[languageTo], languageTo))
  1844. raise RuntimeError("Couldn't change language to %s" % languageTo)
  1845. self.vc.device.press('BACK')
  1846. self.vc.sleep(1)
  1847. self.vc.device.press('BACK')
  1848. class UiCollection():
  1849. '''
  1850. Used to enumerate a container's user interface (UI) elements for the purpose of counting, or
  1851. targeting a sub elements by a child's text or description.
  1852. '''
  1853. pass
  1854. class UiScrollable(UiCollection):
  1855. '''
  1856. A L{UiCollection} that supports searching for items in scrollable layout elements.
  1857. This class can be used with horizontally or vertically scrollable controls.
  1858. '''
  1859. def __init__(self, view):
  1860. self.vc = None
  1861. self.view = view
  1862. self.vertical = True
  1863. self.bounds = view.getBounds()
  1864. (self.x, self.y, self.w, self.h) = view.getPositionAndSize()
  1865. self.steps = 10
  1866. self.duration = 500
  1867. self.swipeDeadZonePercentage = 0.1
  1868. self.maxSearchSwipes = 10
  1869. def flingBackward(self):
  1870. if self.vertical:
  1871. s = (self.x + self.w/2, self.y + self.h * self.swipeDeadZonePercentage)
  1872. e = (self.x + self.w/2, self.y + self.h - self.h * self.swipeDeadZonePercentage)
  1873. else:
  1874. raise RuntimeError('Not implemented yet')
  1875. if DEBUG:
  1876. print >> sys.stderr, "flingBackward: view=", self.view.__smallStr__(), self.view.getPositionAndSize()
  1877. print >> sys.stderr, "self.view.device.drag(%s, %s, %s, %s)" % (s, e, self.duration, self.steps)
  1878. self.view.device.drag(s, e, self.duration, self.steps, self.view.device.display['orientation'])
  1879. def flingForward(self):
  1880. if self.vertical:
  1881. s = (self.x + self.w/2, (self.y + self.h ) - self.h * self.swipeDeadZonePercentage)
  1882. e = (self.x + self.w/2, self.y + self.h * self.swipeDeadZonePercentage)
  1883. else:
  1884. raise RuntimeError('Not implemented yet')
  1885. if DEBUG:
  1886. print >> sys.stderr, "flingForward: view=", self.view.__smallStr__(), self.view.getPositionAndSize()
  1887. print >> sys.stderr, "self.view.device.drag(%s, %s, %s, %s)" % (s, e, self.duration, self.steps)
  1888. self.view.device.drag(s, e, self.duration, self.steps, self.view.device.display['orientation'])
  1889. def flingToBeginning(self, maxSwipes=10):
  1890. if self.vertical:
  1891. for _ in range(maxSwipes):
  1892. if DEBUG:
  1893. print >> sys.stderr, "flinging to beginning"
  1894. self.flingBackward()
  1895. def flingToEnd(self, maxSwipes=10):
  1896. if self.vertical:
  1897. for _ in range(maxSwipes):
  1898. if DEBUG:
  1899. print >> sys.stderr, "flinging to end"
  1900. self.flingForward()
  1901. def scrollTextIntoView(self, text):
  1902. '''
  1903. Performs a forward scroll action on the scrollable layout element until the text you provided is visible,
  1904. or until swipe attempts have been exhausted. See setMaxSearchSwipes(int)
  1905. '''
  1906. if self.vc is None:
  1907. raise ValueError('vc must be set in order to use this method')
  1908. for n in range(self.maxSearchSwipes):
  1909. # FIXME: now I need to figure out the best way of navigating to the ViewClient asossiated
  1910. # with this UiScrollable.
  1911. # It's using setViewClient() now.
  1912. if DEBUG:
  1913. for v in self.vc.views:
  1914. try:
  1915. print >> sys.stderr, " scrollTextIntoView: v=", v.getId(),
  1916. print >> sys.stderr, v.getText()
  1917. except:
  1918. pass
  1919. #v = self.vc.findViewWithText(text, root=self.view)
  1920. v = self.vc.findViewWithText(text)
  1921. if v is not None:
  1922. return v
  1923. self.flingForward()
  1924. #self.vc.sleep(1)
  1925. self.vc.dump(-1)
  1926. # WARNING: after this dump, the value kept in self.view is outdated, it should be refreshed
  1927. # in some way
  1928. return None
  1929. def setAsHorizontalList(self):
  1930. self.vertical = False
  1931. def setAsVerticalList(self):
  1932. self.vertical = True
  1933. def setMaxSearchSwipes(self, maxSwipes):
  1934. self.maxSearchSwipes = maxSwipes
  1935. def setViewClient(self, vc):
  1936. self.vc = vc
  1937. class ListView(View):
  1938. '''
  1939. ListView class.
  1940. '''
  1941. pass
  1942. class UiAutomator2AndroidViewClient():
  1943. '''
  1944. UiAutomator XML to AndroidViewClient
  1945. '''
  1946. def __init__(self, device, version):
  1947. self.device = device
  1948. self.version = version
  1949. self.root = None
  1950. self.nodeStack = []
  1951. self.parent = None
  1952. self.views = []
  1953. self.idCount = 1
  1954. def StartElement(self, name, attributes):
  1955. '''
  1956. Expat start element event handler
  1957. '''
  1958. if name == 'hierarchy':
  1959. pass
  1960. elif name == 'node':
  1961. # Instantiate an Element object
  1962. attributes['uniqueId'] = 'id/no_id/%d' % self.idCount
  1963. bounds = re.split('[\][,]', attributes['bounds'])
  1964. attributes['bounds'] = ((int(bounds[1]), int(bounds[2])), (int(bounds[4]), int(bounds[5])))
  1965. if DEBUG_BOUNDS:
  1966. print >> sys.stderr, "bounds=", attributes['bounds']
  1967. self.idCount += 1
  1968. child = View.factory(attributes, self.device, self.version)
  1969. self.views.append(child)
  1970. # Push element onto the stack and make it a child of parent
  1971. if not self.nodeStack:
  1972. self.root = child
  1973. else:
  1974. self.parent = self.nodeStack[-1]
  1975. self.parent.add(child)
  1976. self.nodeStack.append(child)
  1977. def EndElement(self, name):
  1978. '''
  1979. Expat end element event handler
  1980. '''
  1981. if name == 'hierarchy':
  1982. pass
  1983. elif name == 'node':
  1984. self.nodeStack.pop()
  1985. def CharacterData(self, data):
  1986. '''
  1987. Expat character data event handler
  1988. '''
  1989. if data.strip():
  1990. data = data.encode()
  1991. element = self.nodeStack[-1]
  1992. element.cdata += data
  1993. def Parse(self, uiautomatorxml):
  1994. # Create an Expat parser
  1995. parser = xml.parsers.expat.ParserCreate() # @UndefinedVariable
  1996. # Set the Expat event handlers to our methods
  1997. parser.StartElementHandler = self.StartElement
  1998. parser.EndElementHandler = self.EndElement
  1999. parser.CharacterDataHandler = self.CharacterData
  2000. # Parse the XML File
  2001. try:
  2002. _ = parser.Parse(uiautomatorxml.encode(encoding='utf-8', errors='replace'), True)
  2003. except xml.parsers.expat.ExpatError, ex: # @UndefinedVariable
  2004. print >>sys.stderr, "ERROR: Offending XML:\n", repr(uiautomatorxml)
  2005. raise RuntimeError(ex)
  2006. return self.root
  2007. class Excerpt2Code():
  2008. ''' Excerpt XML to code '''
  2009. def __init__(self):
  2010. self.data = None
  2011. def StartElement(self, name, attributes):
  2012. '''
  2013. Expat start element event handler
  2014. '''
  2015. if name == 'excerpt':
  2016. pass
  2017. else:
  2018. warnings.warn("Unexpected element: '%s'" % name)
  2019. def EndElement(self, name):
  2020. '''
  2021. Expat end element event handler
  2022. '''
  2023. if name == 'excerpt':
  2024. pass
  2025. def CharacterData(self, data):
  2026. '''
  2027. Expat character data event handler
  2028. '''
  2029. if data.strip():
  2030. data = data.encode()
  2031. if not self.data:
  2032. self.data = data
  2033. else:
  2034. self.data += data
  2035. def Parse(self, excerpt):
  2036. # Create an Expat parser
  2037. parser = xml.parsers.expat.ParserCreate() # @UndefinedVariable
  2038. # Set the Expat event handlers to our methods
  2039. parser.StartElementHandler = self.StartElement
  2040. parser.EndElementHandler = self.EndElement
  2041. parser.CharacterDataHandler = self.CharacterData
  2042. # Parse the XML
  2043. _ = parser.Parse(excerpt, 1)
  2044. return self.data
  2045. class ViewClientOptions:
  2046. '''
  2047. ViewClient options helper class
  2048. '''
  2049. DEVIDE = 'device'
  2050. SERIALNO = 'serialno'
  2051. AUTO_DUMP = 'autodump'
  2052. FORCE_VIEW_SERVER_USE = 'forceviewserveruse'
  2053. LOCAL_PORT = 'localport' # ViewServer local port
  2054. REMOTE_PORT = 'remoteport' # ViewServer remote port
  2055. START_VIEW_SERVER = 'startviewserver'
  2056. IGNORE_UIAUTOMATOR_KILLED = 'ignoreuiautomatorkilled'
  2057. COMPRESSED_DUMP = 'compresseddump'
  2058. class ViewClient:
  2059. '''
  2060. ViewClient is a I{ViewServer} client.
  2061. ViewServer backend
  2062. ==================
  2063. If not running the ViewServer is started on the target device or emulator and then the port
  2064. mapping is created.
  2065. UiAutomator backend
  2066. ===================
  2067. No service is started.
  2068. '''
  2069. imageDirectory = None
  2070. ''' The directory used to store screenshot images '''
  2071. def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse=False, localport=VIEW_SERVER_PORT, remoteport=VIEW_SERVER_PORT, startviewserver=True, ignoreuiautomatorkilled=False, compresseddump=True):
  2072. '''
  2073. Constructor
  2074. @type device: AdbClient
  2075. @param device: The device running the C{View server} to which this client will connect
  2076. @type serialno: str
  2077. @param serialno: the serial number of the device or emulator to connect to
  2078. @type adb: str
  2079. @param adb: the path of the C{adb} executable or None and C{ViewClient} will try to find it
  2080. @type autodump: boolean
  2081. @param autodump: whether an automatic dump is performed at the end of this constructor
  2082. @type forceviewserveruse: boolean
  2083. @param forceviewserveruse: Force the use of C{ViewServer} even if the conditions to use
  2084. C{UiAutomator} are satisfied
  2085. @type localport: int
  2086. @param localport: the local port used in the redirection
  2087. @type remoteport: int
  2088. @param remoteport: the remote port used to start the C{ViewServer} in the device or
  2089. emulator
  2090. @type startviewserver: boolean
  2091. @param startviewserver: Whether to start the B{global} ViewServer
  2092. @type ignoreuiautomatorkilled: boolean
  2093. @param ignoreuiautomatorkilled: Ignores received B{Killed} message from C{uiautomator}
  2094. @type compresseddump: boolean
  2095. @param compresseddump: turns --compressed flag for uiautomator dump on/off
  2096. '''
  2097. if not device:
  2098. raise Exception('Device is not connected')
  2099. self.device = device
  2100. ''' The C{AdbClient} device instance '''
  2101. if not serialno:
  2102. raise ValueError("Serialno cannot be None")
  2103. self.serialno = self.__mapSerialNo(serialno)
  2104. ''' The serial number of the device '''
  2105. if DEBUG_DEVICE: print >> sys.stderr, "ViewClient: using device with serialno", self.serialno
  2106. if adb:
  2107. if not os.access(adb, os.X_OK):
  2108. raise Exception('adb="%s" is not executable' % adb)
  2109. else:
  2110. # Using adbclient we don't need adb executable yet (maybe it's needed if we want to
  2111. # start adb if not running)
  2112. adb = obtainAdbPath()
  2113. self.adb = adb
  2114. ''' The adb command '''
  2115. self.root = None
  2116. ''' The root node '''
  2117. self.viewsById = {}
  2118. ''' The map containing all the L{View}s indexed by their L{View.getUniqueId()} '''
  2119. self.display = {}
  2120. ''' The map containing the device's display properties: width, height and density '''
  2121. for prop in [ 'width', 'height', 'density', 'orientation' ]:
  2122. self.display[prop] = -1
  2123. if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES:
  2124. try:
  2125. self.display[prop] = device.display[prop]
  2126. except:
  2127. if WARNINGS:
  2128. warnings.warn("Couldn't determine display %s" % prop)
  2129. else:
  2130. # these values are usually not defined as properties, so we stick to the -1 set
  2131. # before
  2132. pass
  2133. self.build = {}
  2134. ''' The map containing the device's build properties: version.sdk, version.release '''
  2135. for prop in [VERSION_SDK_PROPERTY, VERSION_RELEASE_PROPERTY]:
  2136. self.build[prop] = -1
  2137. try:
  2138. if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES:
  2139. self.build[prop] = device.getProperty(prop)
  2140. else:
  2141. self.build[prop] = device.shell('getprop ro.build.' + prop)[:-2]
  2142. except:
  2143. if WARNINGS:
  2144. warnings.warn("Couldn't determine build %s" % prop)
  2145. if prop == VERSION_SDK_PROPERTY:
  2146. # we expect it to be an int
  2147. self.build[prop] = int(self.build[prop] if self.build[prop] else -1)
  2148. self.ro = {}
  2149. ''' The map containing the device's ro properties: secure, debuggable '''
  2150. for prop in ['secure', 'debuggable']:
  2151. try:
  2152. self.ro[prop] = device.shell('getprop ro.' + prop)[:-2]
  2153. except:
  2154. if WARNINGS:
  2155. warnings.warn("Couldn't determine ro %s" % prop)
  2156. self.ro[prop] = 'UNKNOWN'
  2157. self.forceViewServerUse = forceviewserveruse
  2158. ''' Force the use of ViewServer even if the conditions to use UiAutomator are satisfied '''
  2159. self.useUiAutomator = (self.build[VERSION_SDK_PROPERTY] >= 16) and not forceviewserveruse # jelly bean 4.1 & 4.2
  2160. if DEBUG:
  2161. print >> sys.stderr, " ViewClient.__init__: useUiAutomator=", self.useUiAutomator, "sdk=", self.build[VERSION_SDK_PROPERTY], "forceviewserveruse=", forceviewserveruse
  2162. ''' If UIAutomator is supported by the device it will be used '''
  2163. self.ignoreUiAutomatorKilled = ignoreuiautomatorkilled
  2164. ''' On some devices (i.e. Nexus 7 running 4.2.2) uiautomator is killed just after generating
  2165. the dump file. In many cases the file is already complete so we can ask to ignore the 'Killed'
  2166. message by setting L{ignoreuiautomatorkilled} to C{True}.
  2167. Changes in v2.3.21 that uses C{/dev/tty} instead of a file may have turned this variable
  2168. unnecessary, however it has been kept for backward compatibility.
  2169. '''
  2170. if self.useUiAutomator:
  2171. self.textProperty = TEXT_PROPERTY_UI_AUTOMATOR
  2172. else:
  2173. if self.build[VERSION_SDK_PROPERTY] <= 10:
  2174. self.textProperty = TEXT_PROPERTY_API_10
  2175. else:
  2176. self.textProperty = TEXT_PROPERTY
  2177. if startviewserver:
  2178. if not self.serviceResponse(device.shell('service call window 3')):
  2179. try:
  2180. self.assertServiceResponse(device.shell('service call window 1 i32 %d' %
  2181. remoteport))
  2182. except:
  2183. msg = 'Cannot start View server.\n' \
  2184. 'This only works on emulator and devices running developer versions.\n' \
  2185. 'Does hierarchyviewer work on your device?\n' \
  2186. 'See https://github.com/dtmilano/AndroidViewClient/wiki/Secure-mode\n\n' \
  2187. 'Device properties:\n' \
  2188. ' ro.secure=%s\n' \
  2189. ' ro.debuggable=%s\n' % (self.ro['secure'], self.ro['debuggable'])
  2190. raise Exception(msg)
  2191. self.localPort = localport
  2192. self.remotePort = remoteport
  2193. # FIXME: it seems there's no way of obtaining the serialno from the MonkeyDevice
  2194. subprocess.check_call([self.adb, '-s', self.serialno, 'forward', 'tcp:%d' % self.localPort,
  2195. 'tcp:%d' % self.remotePort])
  2196. self.windows = None
  2197. ''' The list of windows as obtained by L{ViewClient.list()} '''
  2198. self.uiDevice = UiDevice(self)
  2199. ''' The L{UiDevice} '''
  2200. ''' The output of compressed dump is different than output of uncompressed one.
  2201. If one requires uncompressed output, this option should be set to False
  2202. '''
  2203. self.compressedDump = compresseddump
  2204. if autodump:
  2205. self.dump()
  2206. def __del__(self):
  2207. # should clean up some things
  2208. pass
  2209. @staticmethod
  2210. def __obtainAdbPath():
  2211. return obtainAdbPath()
  2212. @staticmethod
  2213. def __mapSerialNo(serialno):
  2214. serialno = serialno.strip()
  2215. #ipRE = re.compile('^\d+\.\d+.\d+.\d+$')
  2216. if IP_RE.match(serialno):
  2217. if DEBUG_DEVICE: print >>sys.stderr, "ViewClient: adding default port to serialno", serialno, ADB_DEFAULT_PORT
  2218. return serialno + ':%d' % ADB_DEFAULT_PORT
  2219. ipPortRE = re.compile('^\d+\.\d+.\d+.\d+:\d+$')
  2220. if ipPortRE.match(serialno):
  2221. # nothing to map
  2222. return serialno
  2223. if re.search("[.*()+]", serialno):
  2224. raise ValueError("Regular expression not supported as serialno in ViewClient. Found '%s'" % serialno)
  2225. return serialno
  2226. @staticmethod
  2227. def __obtainDeviceSerialNumber(device):
  2228. if DEBUG_DEVICE: print >>sys.stderr, "ViewClient: obtaining serial number for connected device"
  2229. serialno = device.getProperty('ro.serialno')
  2230. if not serialno:
  2231. serialno = device.shell('getprop ro.serialno')
  2232. if serialno:
  2233. serialno = serialno[:-2]
  2234. if not serialno:
  2235. qemu = device.shell('getprop ro.kernel.qemu')
  2236. if qemu:
  2237. qemu = qemu[:-2]
  2238. if qemu and int(qemu) == 1:
  2239. # FIXME !!!!!
  2240. # this must be calculated from somewhere, though using a fixed serialno for now
  2241. warnings.warn("Running on emulator but no serial number was specified then 'emulator-5554' is used")
  2242. serialno = 'emulator-5554'
  2243. if not serialno:
  2244. # If there's only one device connected get its serialno
  2245. adb = ViewClient.__obtainAdbPath()
  2246. if DEBUG_DEVICE: print >>sys.stderr, " using adb=%s" % adb
  2247. s = subprocess.Popen([adb, 'get-serialno'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={}).communicate()[0][:-1]
  2248. if s != 'unknown':
  2249. serialno = s
  2250. if DEBUG_DEVICE: print >>sys.stderr, " serialno=%s" % serialno
  2251. if not serialno:
  2252. warnings.warn("Couldn't obtain the serialno of the connected device")
  2253. return serialno
  2254. @staticmethod
  2255. def setAlarm(timeout):
  2256. osName = platform.system()
  2257. if osName.startswith('Windows'): # alarm is not implemented in Windows
  2258. return
  2259. signal.alarm(timeout)
  2260. @staticmethod
  2261. def connectToDeviceOrExit(timeout=60, verbose=False, ignoresecuredevice=False, ignoreversioncheck=False, serialno=None):
  2262. '''
  2263. Connects to a device which serial number is obtained from the script arguments if available
  2264. or using the default regex C{.*}.
  2265. If the connection is not successful the script exits.
  2266. History
  2267. -------
  2268. In MonkeyRunner times, this method was a way of overcoming one of its limitations.
  2269. L{MonkeyRunner.waitForConnection()} returns a L{MonkeyDevice} even if the connection failed.
  2270. Then, to detect this situation, C{device.wake()} is attempted and if it fails then it is
  2271. assumed the previous connection failed.
  2272. @type timeout: int
  2273. @param timeout: timeout for the connection
  2274. @type verbose: bool
  2275. @param verbose: Verbose output
  2276. @type ignoresecuredevice: bool
  2277. @param ignoresecuredevice: Ignores the check for a secure device
  2278. @type ignoreversioncheck: bool
  2279. @param ignoreversioncheck: Ignores the check for a supported ADB version
  2280. @type serialno: str
  2281. @param serialno: The device or emulator serial number
  2282. @return: the device and serialno used for the connection
  2283. '''
  2284. progname = os.path.basename(sys.argv[0])
  2285. if serialno is None:
  2286. # eat all the extra options the invoking script may have added
  2287. args = sys.argv
  2288. while len(args) > 1 and args[1][0] == '-':
  2289. args.pop(1)
  2290. serialno = args[1] if len(args) > 1 else \
  2291. os.environ['ANDROID_SERIAL'] if os.environ.has_key('ANDROID_SERIAL') \
  2292. else '.*'
  2293. if IP_RE.match(serialno):
  2294. # If matches an IP address format and port was not specified add the default
  2295. serialno += ':%d' % ADB_DEFAULT_PORT
  2296. if verbose:
  2297. print >> sys.stderr, 'Connecting to a device with serialno=%s with a timeout of %d secs...' % \
  2298. (serialno, timeout)
  2299. ViewClient.setAlarm(timeout+5)
  2300. device = adbclient.AdbClient(serialno, ignoreversioncheck=ignoreversioncheck)
  2301. ViewClient.setAlarm(0)
  2302. if verbose:
  2303. print >> sys.stderr, 'Connected to device with serialno=%s' % serialno
  2304. secure = device.getSystemProperty('ro.secure')
  2305. debuggable = device.getSystemProperty('ro.debuggable')
  2306. versionProperty = device.getProperty(VERSION_SDK_PROPERTY)
  2307. if versionProperty:
  2308. version = int(versionProperty)
  2309. else:
  2310. if verbose:
  2311. print "Couldn't obtain device SDK version"
  2312. version = -1
  2313. # we are going to use UiAutomator for versions >= 16 that's why we ignore if the device
  2314. # is secure if this is true
  2315. if secure == '1' and debuggable == '0' and not ignoresecuredevice and version < 16:
  2316. print >> sys.stderr, "%s: ERROR: Device is secure, AndroidViewClient won't work." % progname
  2317. if verbose:
  2318. print >> sys.stderr, " secure=%s debuggable=%s version=%d ignoresecuredevice=%s" % \
  2319. (secure, debuggable, version, ignoresecuredevice)
  2320. sys.exit(2)
  2321. if re.search("[.*()+]", serialno) and not re.search("(\d{1,3}\.){3}\d{1,3}", serialno):
  2322. # if a regex was used we have to determine the serialno used
  2323. serialno = ViewClient.__obtainDeviceSerialNumber(device)
  2324. if verbose:
  2325. print >> sys.stderr, 'Actual device serialno=%s' % serialno
  2326. return device, serialno
  2327. @staticmethod
  2328. def traverseShowClassIdAndText(view, extraInfo=None, noExtraInfo=None, extraAction=None):
  2329. '''
  2330. Shows the View class, id and text if available.
  2331. This function can be used as a transform function to L{ViewClient.traverse()}
  2332. @type view: I{View}
  2333. @param view: the View
  2334. @type extraInfo: method
  2335. @param extraInfo: the View method to add extra info
  2336. @type noExtraInfo: bool
  2337. @param noExtraInfo: Don't add extra info
  2338. @type extraAction: method
  2339. @param extraAction: An extra action to be invoked for every view
  2340. @return: the string containing class, id, and text if available
  2341. '''
  2342. try:
  2343. eis = ''
  2344. if extraInfo:
  2345. eis = extraInfo(view)
  2346. if not eis and noExtraInfo:
  2347. eis = noExtraInfo
  2348. if eis:
  2349. eis = ' {0}'.format(eis)
  2350. if extraAction:
  2351. extraAction(view)
  2352. _str = unicode(view.getClass())
  2353. _str += ' '
  2354. _str += view.getId()
  2355. _str += ' '
  2356. _str += view.getText() if view.getText() else ''
  2357. _str += eis
  2358. return _str
  2359. except Exception, e:
  2360. import traceback
  2361. return u'Exception in view=%s: %s:%s\n%s' % (view.__smallStr__(), sys.exc_info()[0].__name__, e, traceback.format_exc())
  2362. @staticmethod
  2363. def traverseShowClassIdTextAndUniqueId(view):
  2364. '''
  2365. Shows the View class, id, text if available and unique id.
  2366. This function can be used as a transform function to L{ViewClient.traverse()}
  2367. @type view: I{View}
  2368. @param view: the View
  2369. @return: the string containing class, id, and text if available and unique Id
  2370. '''
  2371. return ViewClient.traverseShowClassIdAndText(view, View.getUniqueId)
  2372. @staticmethod
  2373. def traverseShowClassIdTextAndContentDescription(view):
  2374. '''
  2375. Shows the View class, id, text if available and content description.
  2376. This function can be used as a transform function to L{ViewClient.traverse()}
  2377. @type view: I{View}
  2378. @param view: the View
  2379. @return: the string containing class, id, and text if available and the content description
  2380. '''
  2381. return ViewClient.traverseShowClassIdAndText(view, View.getContentDescription, 'NAF')
  2382. @staticmethod
  2383. def traverseShowClassIdTextAndTag(view):
  2384. '''
  2385. Shows the View class, id, text if available and tag.
  2386. This function can be used as a transform function to L{ViewClient.traverse()}
  2387. @type view: I{View}
  2388. @param view: the View
  2389. @return: the string containing class, id, and text if available and tag
  2390. '''
  2391. return ViewClient.traverseShowClassIdAndText(view, View.getTag, None)
  2392. @staticmethod
  2393. def traverseShowClassIdTextContentDescriptionAndScreenshot(view):
  2394. '''
  2395. Shows the View class, id, text if available and unique id and takes the screenshot.
  2396. This function can be used as a transform function to L{ViewClient.traverse()}
  2397. @type view: I{View}
  2398. @param view: the View
  2399. @return: the string containing class, id, and text if available and the content description
  2400. '''
  2401. return ViewClient.traverseShowClassIdAndText(view, View.getContentDescription, 'NAF', extraAction=ViewClient.writeViewImageToFileInDir)
  2402. @staticmethod
  2403. def traverseShowClassIdTextAndCenter(view):
  2404. '''
  2405. Shows the View class, id and text if available and center.
  2406. This function can be used as a transform function to L{ViewClient.traverse()}
  2407. @type view: I{View}
  2408. @param view: the View
  2409. @return: the string containing class, id, and text if available
  2410. '''
  2411. return ViewClient.traverseShowClassIdAndText(view, View.getCenter)
  2412. @staticmethod
  2413. def traverseShowClassIdTextPositionAndSize(view):
  2414. '''
  2415. Shows the View class, id and text if available.
  2416. This function can be used as a transform function to L{ViewClient.traverse()}
  2417. @type view: I{View}
  2418. @param view: the View
  2419. @return: the string containing class, id, and text if available
  2420. '''
  2421. return ViewClient.traverseShowClassIdAndText(view, View.getPositionAndSize)
  2422. @staticmethod
  2423. def traverseShowClassIdTextAndBounds(view):
  2424. '''
  2425. Shows the View class, id and text if available.
  2426. This function can be used as a transform function to L{ViewClient.traverse()}
  2427. @type view: I{View}
  2428. @param view: the View
  2429. @return: the string containing class, id, and text if available plus
  2430. View bounds
  2431. '''
  2432. return ViewClient.traverseShowClassIdAndText(view, View.getBounds)
  2433. @staticmethod
  2434. def traverseTakeScreenshot(view):
  2435. '''
  2436. Don't show any any, just takes the screenshot.
  2437. This function can be used as a transform function to L{ViewClient.traverse()}
  2438. @type view: I{View}
  2439. @param view: the View
  2440. @return: None
  2441. '''
  2442. return ViewClient.writeViewImageToFileInDir(view)
  2443. # methods that can be used to transform ViewClient.traverse output
  2444. TRAVERSE_CIT = traverseShowClassIdAndText
  2445. ''' An alias for L{traverseShowClassIdAndText(view)} '''
  2446. TRAVERSE_CITUI = traverseShowClassIdTextAndUniqueId
  2447. ''' An alias for L{traverseShowClassIdTextAndUniqueId(view)} '''
  2448. TRAVERSE_CITCD = traverseShowClassIdTextAndContentDescription
  2449. ''' An alias for L{traverseShowClassIdTextAndContentDescription(view)} '''
  2450. TRAVERSE_CITG = traverseShowClassIdTextAndTag
  2451. ''' An alias for L{traverseShowClassIdTextAndTag(view)} '''
  2452. TRAVERSE_CITC = traverseShowClassIdTextAndCenter
  2453. ''' An alias for L{traverseShowClassIdTextAndCenter(view)} '''
  2454. TRAVERSE_CITPS = traverseShowClassIdTextPositionAndSize
  2455. ''' An alias for L{traverseShowClassIdTextPositionAndSize(view)} '''
  2456. TRAVERSE_CITB = traverseShowClassIdTextAndBounds
  2457. ''' An alias for L{traverseShowClassIdTextAndBounds(view)} '''
  2458. TRAVERSE_CITCDS = traverseShowClassIdTextContentDescriptionAndScreenshot
  2459. ''' An alias for L{traverseShowClassIdTextContentDescriptionAndScreenshot(view)} '''
  2460. TRAVERSE_S = traverseTakeScreenshot
  2461. ''' An alias for L{traverseTakeScreenshot(view)} '''
  2462. @staticmethod
  2463. def sleep(secs=1.0):
  2464. '''
  2465. Sleeps for the specified number of seconds.
  2466. @type secs: float
  2467. @param secs: number of seconds
  2468. '''
  2469. time.sleep(secs)
  2470. def assertServiceResponse(self, response):
  2471. '''
  2472. Checks whether the response received from the server is correct or raises and Exception.
  2473. @type response: str
  2474. @param response: Response received from the server
  2475. @raise Exception: If the response received from the server is invalid
  2476. '''
  2477. if not self.serviceResponse(response):
  2478. raise Exception('Invalid response received from service.')
  2479. def serviceResponse(self, response):
  2480. '''
  2481. Checks the response received from the I{ViewServer}.
  2482. @return: C{True} if the response received matches L{PARCEL_TRUE}, C{False} otherwise
  2483. '''
  2484. PARCEL_TRUE = "Result: Parcel(00000000 00000001 '........')\r\n"
  2485. ''' The TRUE response parcel '''
  2486. if DEBUG:
  2487. print >>sys.stderr, "serviceResponse: comparing '%s' vs Parcel(%s)" % (response, PARCEL_TRUE)
  2488. return response == PARCEL_TRUE
  2489. def setViews(self, received, windowId=None):
  2490. '''
  2491. Sets L{self.views} to the received value splitting it into lines.
  2492. @type received: str
  2493. @param received: the string received from the I{View Server}
  2494. '''
  2495. if not received or received == "":
  2496. raise ValueError("received is empty")
  2497. self.views = []
  2498. ''' 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()}. '''
  2499. self.__parseTree(received.split("\n"), windowId)
  2500. if DEBUG:
  2501. print >>sys.stderr, "there are %d views in this dump" % len(self.views)
  2502. def setViewsFromUiAutomatorDump(self, received):
  2503. '''
  2504. Sets L{self.views} to the received value parsing the received XML.
  2505. @type received: str
  2506. @param received: the string received from the I{UI Automator}
  2507. '''
  2508. if not received or received == "":
  2509. raise ValueError("received is empty")
  2510. self.views = []
  2511. ''' 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()}. '''
  2512. self.__parseTreeFromUiAutomatorDump(received)
  2513. if DEBUG:
  2514. print >>sys.stderr, "there are %d views in this dump" % len(self.views)
  2515. def __splitAttrs(self, strArgs):
  2516. '''
  2517. Splits the C{View} attributes in C{strArgs} and optionally adds the view id to the C{viewsById} list.
  2518. Unique Ids
  2519. ==========
  2520. It is very common to find C{View}s having B{NO_ID} as the Id. This turns very difficult to
  2521. use L{self.findViewById()}. To help in this situation this method assigns B{unique Ids}.
  2522. The B{unique Ids} are generated using the pattern C{id/no_id/<number>} with C{<number>} starting
  2523. at 1.
  2524. @type strArgs: str
  2525. @param strArgs: the string containing the raw list of attributes and values
  2526. @return: Returns the attributes map.
  2527. '''
  2528. if self.useUiAutomator:
  2529. raise RuntimeError("This method is not compatible with UIAutomator")
  2530. # replace the spaces in text:mText to preserve them in later split
  2531. # they are translated back after the attribute matches
  2532. textRE = re.compile('%s=%s,' % (self.textProperty, _nd('len')))
  2533. m = textRE.search(strArgs)
  2534. if m:
  2535. __textStart = m.end()
  2536. __textLen = int(m.group('len'))
  2537. __textEnd = m.end() + __textLen
  2538. s1 = strArgs[__textStart:__textEnd]
  2539. s2 = s1.replace(' ', WS)
  2540. strArgs = strArgs.replace(s1, s2, 1)
  2541. idRE = re.compile("(?P<viewId>id/\S+)")
  2542. attrRE = re.compile('%s(?P<parens>\(\))?=%s,(?P<val>[^ ]*)' % (_ns('attr'), _nd('len')), flags=re.DOTALL)
  2543. hashRE = re.compile('%s@%s' % (_ns('class'), _nh('oid')))
  2544. attrs = {}
  2545. viewId = None
  2546. m = idRE.search(strArgs)
  2547. if m:
  2548. viewId = m.group('viewId')
  2549. if DEBUG:
  2550. print >>sys.stderr, "found view with id=%s" % viewId
  2551. for attr in strArgs.split():
  2552. m = attrRE.match(attr)
  2553. if m:
  2554. __attr = m.group('attr')
  2555. __parens = '()' if m.group('parens') else ''
  2556. __len = int(m.group('len'))
  2557. __val = m.group('val')
  2558. if WARNINGS and __len != len(__val):
  2559. warnings.warn("Invalid len: expected: %d found: %d s=%s e=%s" % (__len, len(__val), __val[:50], __val[-50:]))
  2560. if __attr == self.textProperty:
  2561. # restore spaces that have been replaced
  2562. __val = __val.replace(WS, ' ')
  2563. attrs[__attr + __parens] = __val
  2564. else:
  2565. m = hashRE.match(attr)
  2566. if m:
  2567. attrs['class'] = m.group('class')
  2568. attrs['oid'] = m.group('oid')
  2569. else:
  2570. if DEBUG:
  2571. print >>sys.stderr, attr, "doesn't match"
  2572. if True: # was assignViewById
  2573. if not viewId:
  2574. # If the view has NO_ID we are assigning a default id here (id/no_id) which is
  2575. # immediately incremented if another view with no id was found before to generate
  2576. # a unique id
  2577. viewId = "id/no_id/1"
  2578. if viewId in self.viewsById:
  2579. # sometimes the view ids are not unique, so let's generate a unique id here
  2580. i = 1
  2581. while True:
  2582. newId = re.sub('/\d+$', '', viewId) + '/%d' % i
  2583. if not newId in self.viewsById:
  2584. break
  2585. i += 1
  2586. viewId = newId
  2587. if DEBUG:
  2588. print >>sys.stderr, "adding viewById %s" % viewId
  2589. # We are assigning a new attribute to keep the original id preserved, which could have
  2590. # been NO_ID repeated multiple times
  2591. attrs['uniqueId'] = viewId
  2592. return attrs
  2593. def __parseTree(self, receivedLines, windowId=None):
  2594. '''
  2595. Parses the View tree contained in L{receivedLines}. The tree is created and the root node assigned to L{self.root}.
  2596. This method also assigns L{self.viewsById} values using L{View.getUniqueId} as the key.
  2597. @type receivedLines: str
  2598. @param receivedLines: the string received from B{View Server}
  2599. '''
  2600. self.root = None
  2601. self.viewsById = {}
  2602. self.views = []
  2603. parent = None
  2604. parents = []
  2605. treeLevel = -1
  2606. newLevel = -1
  2607. lastView = None
  2608. for v in receivedLines:
  2609. if v == '' or v == 'DONE' or v == 'DONE.':
  2610. break
  2611. attrs = self.__splitAttrs(v)
  2612. if not self.root:
  2613. if v[0] == ' ':
  2614. raise Exception("Unexpected root element starting with ' '.")
  2615. self.root = View.factory(attrs, self.device, self.build[VERSION_SDK_PROPERTY], self.forceViewServerUse, windowId)
  2616. if DEBUG: self.root.raw = v
  2617. treeLevel = 0
  2618. newLevel = 0
  2619. lastView = self.root
  2620. parent = self.root
  2621. parents.append(parent)
  2622. else:
  2623. newLevel = (len(v) - len(v.lstrip()))
  2624. if newLevel == 0:
  2625. raise Exception("newLevel==0 treeLevel=%d but tree can have only one root, v=%s" % (treeLevel, v))
  2626. child = View.factory(attrs, self.device, self.build[VERSION_SDK_PROPERTY], self.forceViewServerUse, windowId)
  2627. if DEBUG: child.raw = v
  2628. if newLevel == treeLevel:
  2629. parent.add(child)
  2630. lastView = child
  2631. elif newLevel > treeLevel:
  2632. if (newLevel - treeLevel) != 1:
  2633. raise Exception("newLevel jumps %d levels, v=%s" % ((newLevel-treeLevel), v))
  2634. parent = lastView
  2635. parents.append(parent)
  2636. parent.add(child)
  2637. lastView = child
  2638. treeLevel = newLevel
  2639. else: # newLevel < treeLevel
  2640. for _ in range(treeLevel - newLevel):
  2641. parents.pop()
  2642. parent = parents.pop()
  2643. parents.append(parent)
  2644. parent.add(child)
  2645. treeLevel = newLevel
  2646. lastView = child
  2647. self.views.append(lastView)
  2648. self.viewsById[lastView.getUniqueId()] = lastView
  2649. def __parseTreeFromUiAutomatorDump(self, receivedXml):
  2650. parser = UiAutomator2AndroidViewClient(self.device, self.build[VERSION_SDK_PROPERTY])
  2651. try:
  2652. start_xml_index = receivedXml.index("<")
  2653. except ValueError:
  2654. raise ValueError("received does not contain valid XML data")
  2655. self.root = parser.Parse(receivedXml[start_xml_index:])
  2656. self.views = parser.views
  2657. self.viewsById = {}
  2658. for v in self.views:
  2659. self.viewsById[v.getUniqueId()] = v
  2660. def getRoot(self):
  2661. '''
  2662. Gets the root node of the C{View} tree
  2663. @return: the root node of the C{View} tree
  2664. '''
  2665. return self.root
  2666. def traverse(self, root="ROOT", indent="", transform=None, stream=sys.stdout):
  2667. '''
  2668. Traverses the C{View} tree and prints its nodes.
  2669. The nodes are printed converting them to string but other transformations can be specified
  2670. by providing a method name as the C{transform} parameter.
  2671. @type root: L{View}
  2672. @param root: the root node from where the traverse starts
  2673. @type indent: str
  2674. @param indent: the indentation string to use to print the nodes
  2675. @type transform: method
  2676. @param transform: a method to use to transform the node before is printed
  2677. '''
  2678. if transform is None:
  2679. # this cannot be a default value, otherwise
  2680. # TypeError: 'staticmethod' object is not callable
  2681. # is raised
  2682. transform = ViewClient.TRAVERSE_CIT
  2683. if type(root) == types.StringType and root == "ROOT":
  2684. root = self.root
  2685. return ViewClient.__traverse(root, indent, transform, stream)
  2686. # if not root:
  2687. # return
  2688. #
  2689. # s = transform(root)
  2690. # if s:
  2691. # print >>stream, "%s%s" % (indent, s)
  2692. #
  2693. # for ch in root.children:
  2694. # self.traverse(ch, indent=indent+" ", transform=transform, stream=stream)
  2695. @staticmethod
  2696. def __traverse(root, indent="", transform=View.__str__, stream=sys.stdout):
  2697. if not root:
  2698. return
  2699. s = transform(root)
  2700. if stream and s:
  2701. ius = "%s%s" % (indent, s if isinstance(s, unicode) else unicode(s, 'utf-8', 'replace'))
  2702. print >>stream, ius.encode('utf-8', 'replace')
  2703. for ch in root.children:
  2704. ViewClient.__traverse(ch, indent=indent+" ", transform=transform, stream=stream)
  2705. def dump(self, window=-1, sleep=1):
  2706. '''
  2707. Dumps the window content.
  2708. Sleep is useful to wait some time before obtaining the new content when something in the
  2709. window has changed.
  2710. @type window: int or str
  2711. @param window: the window id or name of the window to dump.
  2712. The B{name} is the package name or the window name (i.e. StatusBar) for
  2713. system windows.
  2714. The window id can be provided as C{int} or C{str}. The C{str} should represent
  2715. and C{int} in either base 10 or 16.
  2716. Use -1 to dump all windows.
  2717. This parameter only is used when the backend is B{ViewServer} and it's
  2718. ignored for B{UiAutomator}.
  2719. @type sleep: int
  2720. @param sleep: sleep in seconds before proceeding to dump the content
  2721. @return: the list of Views as C{str} received from the server after being split into lines
  2722. '''
  2723. if sleep > 0:
  2724. time.sleep(sleep)
  2725. if self.useUiAutomator:
  2726. # NOTICE:
  2727. # Using /dev/tty this works even on devices with no sdcard
  2728. received = unicode(self.device.shell('uiautomator dump %s /dev/tty >/dev/null' % ('--compressed' if self.getSdkVersion() >= 18 and self.compressedDump else '')), encoding='utf-8', errors='replace')
  2729. if not received:
  2730. raise RuntimeError('ERROR: Empty UiAutomator dump was received')
  2731. if DEBUG:
  2732. self.received = received
  2733. if DEBUG_RECEIVED:
  2734. print >>sys.stderr, "received %d chars" % len(received)
  2735. print >>sys.stderr
  2736. print >>sys.stderr, repr(received)
  2737. print >>sys.stderr
  2738. onlyKilledRE = re.compile('[\n\S]*Killed[\n\r\S]*', re.MULTILINE)
  2739. if onlyKilledRE.search(received):
  2740. MONKEY = 'com.android.commands.monkey'
  2741. extraInfo = ''
  2742. if self.device.shell('ps | grep "%s"' % MONKEY):
  2743. extraInfo = "\nIt is know that '%s' conflicts with 'uiautomator'. Please kill it and try again." % MONKEY
  2744. raise RuntimeError('''ERROR: UiAutomator output contains no valid information. UiAutomator was killed, no reason given.''' + extraInfo)
  2745. if self.ignoreUiAutomatorKilled:
  2746. if DEBUG_RECEIVED:
  2747. print >>sys.stderr, "ignoring UiAutomator Killed"
  2748. killedRE = re.compile('</hierarchy>[\n\S]*Killed', re.MULTILINE)
  2749. if killedRE.search(received):
  2750. received = re.sub(killedRE, '</hierarchy>', received)
  2751. elif DEBUG_RECEIVED:
  2752. print "UiAutomator Killed: NOT FOUND!"
  2753. # It seems that API18 uiautomator spits this message to stdout
  2754. dumpedToDevTtyRE = re.compile('</hierarchy>[\n\S]*UI hierchary dumped to: /dev/tty.*', re.MULTILINE)
  2755. if dumpedToDevTtyRE.search(received):
  2756. received = re.sub(dumpedToDevTtyRE, '</hierarchy>', received)
  2757. if DEBUG_RECEIVED:
  2758. print >>sys.stderr, "received=", received
  2759. # API19 seems to send this warning as part of the XML.
  2760. # Let's remove it if present
  2761. received = received.replace('WARNING: linker: libdvm.so has text relocations. This is wasting memory and is a security risk. Please fix.\r\n', '')
  2762. if re.search('\[: not found', received):
  2763. raise RuntimeError('''ERROR: Some emulator images (i.e. android 4.1.2 API 16 generic_x86) does not include the '[' command.
  2764. While UiAutomator back-end might be supported 'uiautomator' command fails.
  2765. You should force ViewServer back-end.''')
  2766. if received.startswith('ERROR: could not get idle state.'):
  2767. # See https://android.googlesource.com/platform/frameworks/testing/+/jb-mr2-release/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
  2768. raise RuntimeError('''The views are being refreshed too frequently to dump.''')
  2769. self.setViewsFromUiAutomatorDump(received)
  2770. else:
  2771. if isinstance(window, str):
  2772. if window != '-1':
  2773. self.list(sleep=0)
  2774. found = False
  2775. for wId in self.windows:
  2776. try:
  2777. if window == self.windows[wId]:
  2778. window = wId
  2779. found = True
  2780. break
  2781. except:
  2782. pass
  2783. try:
  2784. if int(window) == wId:
  2785. window = wId
  2786. found = True
  2787. break
  2788. except:
  2789. pass
  2790. try:
  2791. if int(window, 16) == wId:
  2792. window = wId
  2793. found = True
  2794. break
  2795. except:
  2796. pass
  2797. if not found:
  2798. raise RuntimeError("ERROR: Cannot find window '%s' in %s" % (window, self.windows))
  2799. else:
  2800. window = -1
  2801. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  2802. try:
  2803. s.connect((VIEW_SERVER_HOST, self.localPort))
  2804. except socket.error, ex:
  2805. raise RuntimeError("ERROR: Connecting to %s:%d: %s" % (VIEW_SERVER_HOST, self.localPort, ex))
  2806. cmd = 'dump %x\r\n' % window
  2807. if DEBUG:
  2808. print >>sys.stderr, "executing: '%s'" % cmd
  2809. s.send(cmd)
  2810. received = ""
  2811. doneRE = re.compile("DONE")
  2812. ViewClient.setAlarm(120)
  2813. while True:
  2814. if DEBUG_RECEIVED:
  2815. print >>sys.stderr, " reading from socket..."
  2816. received += s.recv(1024)
  2817. if doneRE.search(received[-7:]):
  2818. break
  2819. s.close()
  2820. ViewClient.setAlarm(0)
  2821. if DEBUG:
  2822. self.received = received
  2823. if DEBUG_RECEIVED:
  2824. print >>sys.stderr, "received %d chars" % len(received)
  2825. print >>sys.stderr
  2826. print >>sys.stderr, received
  2827. print >>sys.stderr
  2828. if received:
  2829. for c in received:
  2830. if ord(c) > 127:
  2831. received = unicode(received, encoding='utf-8', errors='replace')
  2832. break
  2833. self.setViews(received, hex(window)[2:])
  2834. if DEBUG_TREE:
  2835. self.traverse(self.root)
  2836. return self.views
  2837. def list(self, sleep=1):
  2838. '''
  2839. List the windows.
  2840. Sleep is useful to wait some time before obtaining the new content when something in the
  2841. window has changed.
  2842. This also sets L{self.windows} as the list of windows.
  2843. @type sleep: int
  2844. @param sleep: sleep in seconds before proceeding to dump the content
  2845. @return: the list of windows
  2846. '''
  2847. if sleep > 0:
  2848. time.sleep(sleep)
  2849. if self.useUiAutomator:
  2850. raise Exception("Not implemented yet: listing windows with UiAutomator")
  2851. else:
  2852. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  2853. try:
  2854. s.connect((VIEW_SERVER_HOST, self.localPort))
  2855. except socket.error, ex:
  2856. raise RuntimeError("ERROR: Connecting to %s:%d: %s" % (VIEW_SERVER_HOST, self.localPort, ex))
  2857. s.send('list\r\n')
  2858. received = ""
  2859. doneRE = re.compile("DONE")
  2860. while True:
  2861. received += s.recv(1024)
  2862. if doneRE.search(received[-7:]):
  2863. break
  2864. s.close()
  2865. if DEBUG:
  2866. self.received = received
  2867. if DEBUG_RECEIVED:
  2868. print >>sys.stderr, "received %d chars" % len(received)
  2869. print >>sys.stderr
  2870. print >>sys.stderr, received
  2871. print >>sys.stderr
  2872. self.windows = {}
  2873. for line in received.split('\n'):
  2874. if not line:
  2875. break
  2876. if doneRE.search(line):
  2877. break
  2878. values = line.split()
  2879. if len(values) > 1:
  2880. package = values[1]
  2881. else:
  2882. package = "UNKNOWN"
  2883. if len(values) > 0:
  2884. wid = values[0]
  2885. else:
  2886. wid = '00000000'
  2887. self.windows[int('0x' + wid, 16)] = package
  2888. return self.windows
  2889. def findViewById(self, viewId, root="ROOT", viewFilter=None):
  2890. '''
  2891. Finds the View with the specified viewId.
  2892. @type viewId: str
  2893. @param viewId: the ID of the view to find
  2894. @type root: str
  2895. @type root: View
  2896. @param root: the root node of the tree where the View will be searched
  2897. @type: viewFilter: function
  2898. @param viewFilter: a function that will be invoked providing the candidate View as a parameter
  2899. and depending on the return value (C{True} or C{False}) the View will be
  2900. selected and returned as the result of C{findViewById()} or ignored.
  2901. This can be C{None} and no extra filtering is applied.
  2902. @return: the C{View} found or C{None}
  2903. '''
  2904. if not root:
  2905. return None
  2906. if type(root) == types.StringType and root == "ROOT":
  2907. return self.findViewById(viewId, self.root, viewFilter)
  2908. if root.getId() == viewId:
  2909. if viewFilter:
  2910. if viewFilter(root):
  2911. return root
  2912. else:
  2913. return root
  2914. if re.match('^id/no_id', viewId) or re.match('^id/.+/.+', viewId):
  2915. if root.getUniqueId() == viewId:
  2916. if viewFilter:
  2917. if viewFilter(root):
  2918. return root;
  2919. else:
  2920. return root
  2921. for ch in root.children:
  2922. foundView = self.findViewById(viewId, ch, viewFilter)
  2923. if foundView:
  2924. if viewFilter:
  2925. if viewFilter(foundView):
  2926. return foundView
  2927. else:
  2928. return foundView
  2929. def findViewByIdOrRaise(self, viewId, root="ROOT", viewFilter=None):
  2930. '''
  2931. Finds the View or raise a ViewNotFoundException.
  2932. @type viewId: str
  2933. @param viewId: the ID of the view to find
  2934. @type root: str
  2935. @type root: View
  2936. @param root: the root node of the tree where the View will be searched
  2937. @type: viewFilter: function
  2938. @param viewFilter: a function that will be invoked providing the candidate View as a parameter
  2939. and depending on the return value (C{True} or C{False}) the View will be
  2940. selected and returned as the result of C{findViewById()} or ignored.
  2941. This can be C{None} and no extra filtering is applied.
  2942. @return: the View found
  2943. @raise ViewNotFoundException: raise the exception if View not found
  2944. '''
  2945. view = self.findViewById(viewId, root, viewFilter)
  2946. if view:
  2947. return view
  2948. else:
  2949. raise ViewNotFoundException("ID", viewId, root)
  2950. def findViewByTag(self, tag, root="ROOT"):
  2951. '''
  2952. Finds the View with the specified tag
  2953. '''
  2954. return self.findViewWithAttribute('getTag()', tag, root)
  2955. def findViewByTagOrRaise(self, tag, root="ROOT"):
  2956. '''
  2957. Finds the View with the specified tag or raise a ViewNotFoundException
  2958. '''
  2959. view = self.findViewWithAttribute('getTag()', tag, root)
  2960. if view:
  2961. return view
  2962. else:
  2963. raise ViewNotFoundException("tag", tag, root)
  2964. def __findViewsWithAttributeInTree(self, attr, val, root):
  2965. # Note the plural in this method name
  2966. matchingViews = []
  2967. if not self.root:
  2968. print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
  2969. return matchingViews
  2970. if type(root) == types.StringType and root == "ROOT":
  2971. root = self.root
  2972. if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: type val=", type(val)
  2973. if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: checking if root=%s has attr=%s == %s" % (root.__smallStr__(), attr, val)
  2974. if root and attr in root.map and root.map[attr] == val:
  2975. if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: FOUND: %s" % root.__smallStr__()
  2976. matchingViews.append(root)
  2977. else:
  2978. for ch in root.children:
  2979. matchingViews += self.__findViewsWithAttributeInTree(attr, val, ch)
  2980. return matchingViews
  2981. def __findViewWithAttributeInTree(self, attr, val, root):
  2982. if DEBUG:
  2983. print >> sys.stderr, " __findViewWithAttributeInTree: type(val)=", type(val)
  2984. if type(val) != types.UnicodeType:
  2985. u = unicode(val, encoding='utf-8', errors='ignore')
  2986. else:
  2987. u = val
  2988. print >> sys.stderr, u'''__findViewWithAttributeInTree({0}'''.format(attr),
  2989. try:
  2990. print >> sys.stderr, u''', {0}'''.format(u),
  2991. except:
  2992. pass
  2993. print >> sys.stderr, u'>>>>>>>>>>>>>>>>>>', type(root)
  2994. if type(root) == types.StringType:
  2995. print >> sys.stderr, u'>>>>>>>>>>>>>>>>>>', root
  2996. print >> sys.stderr, u''', {0})'''.format(root)
  2997. else:
  2998. print >> sys.stderr, u''', {0})'''.format(root.__smallStr__())
  2999. if not self.root:
  3000. print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
  3001. return None
  3002. if type(root) == types.StringType and root == "ROOT":
  3003. root = self.root
  3004. if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: type val=", type(val)
  3005. if DEBUG:
  3006. #print >> sys.stderr, u'''__findViewWithAttributeInTree: checking if root={0}: '''.format(root),
  3007. print >> sys.stderr, u'''has {0} == '''.format(attr),
  3008. if type(val) == types.UnicodeType:
  3009. u = val
  3010. else:
  3011. u = unicode(val, encoding='utf-8', errors='replace')
  3012. try:
  3013. print >> sys.stderr, u'''{0}'''.format(u)
  3014. except:
  3015. pass
  3016. if isinstance(val, RegexType):
  3017. return self.__findViewWithAttributeInTreeThatMatches(attr, val, root)
  3018. else:
  3019. try:
  3020. if DEBUG:
  3021. print >> sys.stderr, u'''__findViewWithAttributeInTree: comparing {0}: '''.format(attr),
  3022. print >> sys.stderr, u'''{0} == '''.format(root.map[attr]),
  3023. print >> sys.stderr, u'''{0}'''.format(val)
  3024. except:
  3025. pass
  3026. if root and attr in root.map and root.map[attr] == val:
  3027. if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: FOUND: %s" % root.__smallStr__()
  3028. return root
  3029. else:
  3030. for ch in root.children:
  3031. v = self.__findViewWithAttributeInTree(attr, val, ch)
  3032. if v:
  3033. return v
  3034. return None
  3035. def __findViewWithAttributeInTreeOrRaise(self, attr, val, root):
  3036. view = self.__findViewWithAttributeInTree(attr, val, root)
  3037. if view:
  3038. return view
  3039. else:
  3040. raise ViewNotFoundException(attr, val, root)
  3041. def __findViewWithAttributeInTreeThatMatches(self, attr, regex, root, rlist=[]):
  3042. if not self.root:
  3043. print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
  3044. return None
  3045. if type(root) == types.StringType and root == "ROOT":
  3046. root = self.root
  3047. if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTreeThatMatches: checking if root=%s attr=%s matches %s" % (root.__smallStr__(), attr, regex)
  3048. if root and attr in root.map and regex.match(root.map[attr]):
  3049. if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTreeThatMatches: FOUND: %s" % root.__smallStr__()
  3050. return root
  3051. #print >>sys.stderr, "appending root=%s to rlist=%s" % (root.__smallStr__(), rlist)
  3052. #return rlist.append(root)
  3053. else:
  3054. for ch in root.children:
  3055. v = self.__findViewWithAttributeInTreeThatMatches(attr, regex, ch, rlist)
  3056. if v:
  3057. return v
  3058. #print >>sys.stderr, "appending v=%s to rlist=%s" % (v.__smallStr__(), rlist)
  3059. #return rlist.append(v)
  3060. return None
  3061. #return rlist
  3062. def findViewWithAttribute(self, attr, val, root="ROOT"):
  3063. '''
  3064. Finds the View with the specified attribute and value
  3065. '''
  3066. if DEBUG:
  3067. try:
  3068. print >> sys.stderr, u'findViewWithAttribute({0}, {1}, {2})'.format(attr, unicode(val, encoding='utf-8', errors='replace'), root)
  3069. except:
  3070. pass
  3071. print >> sys.stderr, " findViewWithAttribute: type(val)=", type(val)
  3072. return self.__findViewWithAttributeInTree(attr, val, root)
  3073. def findViewsWithAttribute(self, attr, val, root="ROOT"):
  3074. '''
  3075. Finds the Views with the specified attribute and value.
  3076. This allows you to see all items that match your criteria in the view hierarchy
  3077. Usage:
  3078. buttons = v.findViewsWithAttribute("class", "android.widget.Button")
  3079. '''
  3080. return self.__findViewsWithAttributeInTree(attr, val, root)
  3081. def findViewWithAttributeOrRaise(self, attr, val, root="ROOT"):
  3082. '''
  3083. Finds the View or raise a ViewNotFoundException.
  3084. @return: the View found
  3085. @raise ViewNotFoundException: raise the exception if View not found
  3086. '''
  3087. view = self.findViewWithAttribute(attr, val, root)
  3088. if view:
  3089. return view
  3090. else:
  3091. raise ViewNotFoundException(attr, val, root)
  3092. def findViewWithAttributeThatMatches(self, attr, regex, root="ROOT"):
  3093. '''
  3094. Finds the list of Views with the specified attribute matching
  3095. regex
  3096. '''
  3097. return self.__findViewWithAttributeInTreeThatMatches(attr, regex, root)
  3098. def findViewWithText(self, text, root="ROOT"):
  3099. if DEBUG:
  3100. try:
  3101. print >>sys.stderr, '''findViewWithText({0}, {1})'''.format(text, root)
  3102. print >> sys.stderr, " findViewWithText: type(text)=", type(text)
  3103. except:
  3104. pass
  3105. if isinstance(text, RegexType):
  3106. return self.findViewWithAttributeThatMatches(self.textProperty, text, root)
  3107. #l = self.findViewWithAttributeThatMatches(TEXT_PROPERTY, text)
  3108. #ll = len(l)
  3109. #if ll == 0:
  3110. # return None
  3111. #elif ll == 1:
  3112. # return l[0]
  3113. #else:
  3114. # print >>sys.stderr, "WARNING: findViewWithAttributeThatMatches invoked by findViewWithText returns %d items." % ll
  3115. # return l
  3116. else:
  3117. return self.findViewWithAttribute(self.textProperty, text, root)
  3118. def findViewWithTextOrRaise(self, text, root="ROOT"):
  3119. '''
  3120. Finds the View or raise a ViewNotFoundException.
  3121. @return: the View found
  3122. @raise ViewNotFoundException: raise the exception if View not found
  3123. '''
  3124. if DEBUG:
  3125. print >>sys.stderr, "findViewWithTextOrRaise(%s, %s)" % (text, root)
  3126. view = self.findViewWithText(text, root)
  3127. if view:
  3128. return view
  3129. else:
  3130. raise ViewNotFoundException("text", text, root)
  3131. def findViewWithContentDescription(self, contentdescription, root="ROOT"):
  3132. '''
  3133. Finds the View with the specified content description
  3134. '''
  3135. return self.__findViewWithAttributeInTree('content-desc', contentdescription, root)
  3136. def findViewWithContentDescriptionOrRaise(self, contentdescription, root="ROOT"):
  3137. '''
  3138. Finds the View with the specified content description
  3139. '''
  3140. return self.__findViewWithAttributeInTreeOrRaise('content-desc', contentdescription, root)
  3141. def findViewsContainingPoint(self, (x, y), _filter=None):
  3142. '''
  3143. Finds the list of Views that contain the point (x, y).
  3144. '''
  3145. if not _filter:
  3146. _filter = lambda v: True
  3147. return [v for v in self.views if (v.containsPoint((x,y)) and _filter(v))]
  3148. def getViewIds(self):
  3149. '''
  3150. @deprecated: Use L{getViewsById} instead.
  3151. Returns the Views map.
  3152. '''
  3153. return self.viewsById
  3154. def getViewsById(self):
  3155. '''
  3156. Returns the Views map. The keys are C{uniqueIds} and the values are C{View}s.
  3157. '''
  3158. return self.viewsById
  3159. def __getFocusedWindowPosition(self):
  3160. return self.__getFocusedWindowId()
  3161. def getSdkVersion(self):
  3162. '''
  3163. Gets the SDK version.
  3164. '''
  3165. return self.build[VERSION_SDK_PROPERTY]
  3166. def isKeyboardShown(self):
  3167. '''
  3168. Whether the keyboard is displayed.
  3169. '''
  3170. return self.device.isKeyboardShown()
  3171. def writeImageToFile(self, filename, _format="PNG", deviceart=None, dropshadow=True, screenglare=True):
  3172. '''
  3173. Write the View image to the specified filename in the specified format.
  3174. @type filename: str
  3175. @param filename: Absolute path and optional filename receiving the image. If this points to
  3176. a directory, then the filename is determined by the serialno of the device and
  3177. format extension.
  3178. @type _format: str
  3179. @param _format: Image format (default format is PNG)
  3180. '''
  3181. filename = self.device.substituteDeviceTemplate(filename)
  3182. if not os.path.isabs(filename):
  3183. raise ValueError("writeImageToFile expects an absolute path (filename='%s')" % filename)
  3184. if os.path.isdir(filename):
  3185. filename = os.path.join(filename, self.serialno + '.' + _format.lower())
  3186. if DEBUG:
  3187. print >> sys.stderr, "writeImageToFile: saving image to '%s' in %s format (reconnect=%s)" % (filename, _format, self.device.reconnect)
  3188. image = self.device.takeSnapshot(reconnect=self.device.reconnect)
  3189. if deviceart:
  3190. if 'STUDIO_DIR' in os.environ:
  3191. PLUGIN_DIR = 'plugins/android/lib/device-art-resources'
  3192. osName = platform.system()
  3193. if osName == 'Darwin':
  3194. deviceArtDir = os.environ['STUDIO_DIR'] + '/Contents/' + PLUGIN_DIR
  3195. else:
  3196. deviceArtDir = os.environ['STUDIO_DIR'] + '/' + PLUGIN_DIR
  3197. # FIXME: should parse XML
  3198. deviceArtXml = deviceArtDir + '/device-art.xml'
  3199. if not os.path.exists(deviceArtXml):
  3200. warnings.warn("Cannot find device art definition file")
  3201. # <device id="nexus_5" name="Nexus 5">
  3202. # <orientation name="port" size="1370,2405" screenPos="144,195" screenSize="1080,1920" shadow="port_shadow.png" back="port_back.png" lights="port_fore.png"/>
  3203. # <orientation name="land" size="2497,1235" screenPos="261,65" screenSize="1920,1080" shadow="land_shadow.png" back="land_back.png" lights="land_fore.png"/>
  3204. # </device>
  3205. orientation = self.display['orientation']
  3206. if orientation == 0 or orientation == 2:
  3207. orientationName = 'port'
  3208. elif orientation == 1 or orientation == 3:
  3209. orientationName = 'land'
  3210. else:
  3211. warnings.warn("Unknown orientation=" + orientation)
  3212. orientationName = 'port'
  3213. separator = '_'
  3214. if deviceart == 'auto':
  3215. hardware = self.device.getProperty('ro.hardware')
  3216. if hardware == 'hammerhead':
  3217. deviceart = 'nexus_5'
  3218. elif hardware == 'mako':
  3219. deviceart = 'nexus_4'
  3220. elif hardware == 'grouper':
  3221. deviceart = 'nexus_7' # 2012
  3222. elif hardware == 'mt5861':
  3223. deviceart = 'tv_1080p'
  3224. if deviceart == 'nexus_5':
  3225. if orientationName == 'port':
  3226. screenPos = (144, 195)
  3227. else:
  3228. screenPos = (261, 65)
  3229. elif deviceart == 'nexus_4':
  3230. if orientationName == 'port':
  3231. screenPos = (94, 187)
  3232. else:
  3233. screenPos = (257, 45)
  3234. elif deviceart == 'nexus_7': # 2012
  3235. if orientationName == 'port':
  3236. screenPos = (142, 190)
  3237. else:
  3238. screenPos = (260, 105)
  3239. elif deviceart == 'tv_1080p':
  3240. screenPos = (85, 59)
  3241. orientationName = ''
  3242. separator = ''
  3243. SUPPORTED_DEVICES = ['nexus_5', 'nexus_4', 'nexus_7', 'tv_1080p']
  3244. if deviceart not in SUPPORTED_DEVICES:
  3245. warnings.warn("Only %s is supported now, more devices coming soon" % SUPPORTED_DEVICES)
  3246. if not os.path.isdir(deviceArtDir + '/' + deviceart):
  3247. warnings.warn("Cannot find device art for " + deviceart + ' at ' + deviceArtDir + '/' + deviceart)
  3248. deviceArtModelDir = deviceArtDir + '/' + deviceart
  3249. try:
  3250. from PIL import Image
  3251. if dropshadow:
  3252. dropShadowImage = Image.open(deviceArtModelDir + '/%s%sshadow.png' % (orientationName, separator))
  3253. deviceBack = Image.open(deviceArtModelDir + '/%s%sback.png' % (orientationName, separator))
  3254. if dropshadow:
  3255. dropShadowImage.paste(deviceBack, (0, 0), deviceBack)
  3256. deviceBack = dropShadowImage
  3257. deviceBack.paste(image, screenPos)
  3258. if screenglare:
  3259. screenGlareImage = Image.open(deviceArtModelDir + '/%s%sfore.png' % (orientationName, separator))
  3260. deviceBack.paste(screenGlareImage, (0, 0), screenGlareImage)
  3261. image = deviceBack
  3262. except ImportError as ex:
  3263. warnings.warn('''PIL or Pillow is needed for image manipulation
  3264. On Ubuntu install
  3265. $ sudo apt-get install python-imaging python-imaging-tk
  3266. On OSX install
  3267. $ brew install homebrew/python/pillow
  3268. ''')
  3269. else:
  3270. warnings.warn("ViewClient.writeImageToFile: Cannot add device art because STUDIO_DIR environment variable was not set")
  3271. image.save(filename, _format)
  3272. @staticmethod
  3273. def writeViewImageToFileInDir(view):
  3274. '''
  3275. Write the View image to the directory specified in C{ViewClient.imageDirectory}.
  3276. @type view: View
  3277. @param view: The view
  3278. '''
  3279. if not ViewClient.imageDirectory:
  3280. raise RuntimeError('You must set ViewClient.imageDiretory in order to use this method')
  3281. view.writeImageToFile(ViewClient.imageDirectory)
  3282. @staticmethod
  3283. def __pickleable(tree):
  3284. '''
  3285. Makes the tree pickleable.
  3286. '''
  3287. def removeDeviceReference(view):
  3288. '''
  3289. Removes the reference to a L{MonkeyDevice}.
  3290. '''
  3291. view.device = None
  3292. ###########################################################################################
  3293. # FIXME: Unfortunatelly deepcopy does not work with MonkeyDevice objects, which is
  3294. # sadly the reason why we cannot pickle the tree and we need to remove the MonkeyDevice
  3295. # references.
  3296. # We wanted to copy the tree to preserve the original and make piclkleable the copy.
  3297. #treeCopy = copy.deepcopy(tree)
  3298. treeCopy = tree
  3299. # IMPORTANT:
  3300. # This assumes that the first element in the list is the tree root
  3301. ViewClient.__traverse(treeCopy[0], transform=removeDeviceReference)
  3302. ###########################################################################################
  3303. return treeCopy
  3304. def distanceTo(self, tree):
  3305. '''
  3306. Calculates the distance between the current state and the tree passed as argument.
  3307. @type tree: list of Views
  3308. @param tree: Tree of Views
  3309. @return: the distance
  3310. '''
  3311. return ViewClient.distance(ViewClient.__pickleable(self.views), tree)
  3312. @staticmethod
  3313. def distance(tree1, tree2):
  3314. '''
  3315. Calculates the distance between the two trees.
  3316. @type tree1: list of Views
  3317. @param tree1: Tree of Views
  3318. @type tree2: list of Views
  3319. @param tree2: Tree of Views
  3320. @return: the distance
  3321. '''
  3322. ################################################################
  3323. #FIXME: this should copy the entire tree and then transform it #
  3324. ################################################################
  3325. pickleableTree1 = ViewClient.__pickleable(tree1)
  3326. pickleableTree2 = ViewClient.__pickleable(tree2)
  3327. s1 = pickle.dumps(pickleableTree1)
  3328. s2 = pickle.dumps(pickleableTree2)
  3329. if DEBUG_DISTANCE:
  3330. print >>sys.stderr, "distance: calculating distance between", s1[:20], "and", s2[:20]
  3331. l1 = len(s1)
  3332. l2 = len(s2)
  3333. t = float(max(l1, l2))
  3334. if l1 == l2:
  3335. if DEBUG_DISTANCE:
  3336. print >>sys.stderr, "distance: trees have same length, using Hamming distance"
  3337. return ViewClient.__hammingDistance(s1, s2)/t
  3338. else:
  3339. if DEBUG_DISTANCE:
  3340. print >>sys.stderr, "distance: trees have different length, using Levenshtein distance"
  3341. return ViewClient.__levenshteinDistance(s1, s2)/t
  3342. @staticmethod
  3343. def __hammingDistance(s1, s2):
  3344. '''
  3345. Finds the Hamming distance between two strings.
  3346. @param s1: string
  3347. @param s2: string
  3348. @return: the distance
  3349. @raise ValueError: if the lenght of the strings differ
  3350. '''
  3351. l1 = len(s1)
  3352. l2 = len(s2)
  3353. if l1 != l2:
  3354. raise ValueError("Hamming distance requires strings of same size.")
  3355. return sum(ch1 != ch2 for ch1, ch2 in zip(s1, s2))
  3356. def hammingDistance(self, tree):
  3357. '''
  3358. Finds the Hamming distance between this tree and the one passed as argument.
  3359. '''
  3360. s1 = ' '.join(map(View.__str__, self.views))
  3361. s2 = ' '.join(map(View.__str__, tree))
  3362. return ViewClient.__hammingDistance(s1, s2)
  3363. @staticmethod
  3364. def __levenshteinDistance(s, t):
  3365. '''
  3366. Find the Levenshtein distance between two Strings.
  3367. Python version of Levenshtein distance method implemented in Java at
  3368. U{http://www.java2s.com/Code/Java/Data-Type/FindtheLevenshteindistancebetweentwoStrings.htm}.
  3369. This is the number of changes needed to change one String into
  3370. another, where each change is a single character modification (deletion,
  3371. insertion or substitution).
  3372. The previous implementation of the Levenshtein distance algorithm
  3373. was from U{http://www.merriampark.com/ld.htm}
  3374. Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError
  3375. which can occur when my Java implementation is used with very large strings.
  3376. This implementation of the Levenshtein distance algorithm
  3377. is from U{http://www.merriampark.com/ldjava.htm}::
  3378. StringUtils.getLevenshteinDistance(null, *) = IllegalArgumentException
  3379. StringUtils.getLevenshteinDistance(*, null) = IllegalArgumentException
  3380. StringUtils.getLevenshteinDistance("","") = 0
  3381. StringUtils.getLevenshteinDistance("","a") = 1
  3382. StringUtils.getLevenshteinDistance("aaapppp", "") = 7
  3383. StringUtils.getLevenshteinDistance("frog", "fog") = 1
  3384. StringUtils.getLevenshteinDistance("fly", "ant") = 3
  3385. StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
  3386. StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
  3387. StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
  3388. StringUtils.getLevenshteinDistance("hello", "hallo") = 1
  3389. @param s: the first String, must not be null
  3390. @param t: the second String, must not be null
  3391. @return: result distance
  3392. @raise ValueError: if either String input C{null}
  3393. '''
  3394. if s is None or t is None:
  3395. raise ValueError("Strings must not be null")
  3396. n = len(s)
  3397. m = len(t)
  3398. if n == 0:
  3399. return m
  3400. elif m == 0:
  3401. return n
  3402. if n > m:
  3403. tmp = s
  3404. s = t
  3405. t = tmp
  3406. n = m;
  3407. m = len(t)
  3408. p = [None]*(n+1)
  3409. d = [None]*(n+1)
  3410. for i in range(0, n+1):
  3411. p[i] = i
  3412. for j in range(1, m+1):
  3413. if DEBUG_DISTANCE:
  3414. if j % 100 == 0:
  3415. print >>sys.stderr, "DEBUG:", int(j/(m+1.0)*100),"%\r",
  3416. t_j = t[j-1]
  3417. d[0] = j
  3418. for i in range(1, n+1):
  3419. cost = 0 if s[i-1] == t_j else 1
  3420. # minimum of cell to the left+1, to the top+1, diagonally left and up +cost
  3421. d[i] = min(min(d[i-1]+1, p[i]+1), p[i-1]+cost)
  3422. _d = p
  3423. p = d
  3424. d = _d
  3425. if DEBUG_DISTANCE:
  3426. print >> sys.stderr, "\n"
  3427. return p[n]
  3428. def levenshteinDistance(self, tree):
  3429. '''
  3430. Finds the Levenshtein distance between this tree and the one passed as argument.
  3431. '''
  3432. s1 = ' '.join(map(View.__microStr__, self.views))
  3433. s2 = ' '.join(map(View.__microStr__, tree))
  3434. return ViewClient.__levenshteinDistance(s1, s2)
  3435. @staticmethod
  3436. def excerpt(_str, execute=False):
  3437. code = Excerpt2Code().Parse(_str)
  3438. if execute:
  3439. exec code
  3440. else:
  3441. return code
  3442. class ConnectedDevice:
  3443. def __init__(self, device, vc, serialno):
  3444. self.device = device
  3445. self.vc = vc
  3446. self.serialno = serialno
  3447. class CulebraOptions:
  3448. '''
  3449. Culebra options helper class
  3450. '''
  3451. HELP = 'help'
  3452. VERBOSE = 'verbose'
  3453. VERSION = 'version'
  3454. IGNORE_SECURE_DEVICE = 'ignore-secure-device'
  3455. IGNORE_VERSION_CHECK = 'ignore-version-check'
  3456. FORCE_VIEW_SERVER_USE = 'force-view-server-use'
  3457. DO_NOT_START_VIEW_SERVER = 'do-not-start-view-server'
  3458. DO_NOT_IGNORE_UIAUTOMATOR_KILLED = 'do-not-ignore-uiautomator-killed'
  3459. FIND_VIEWS_BY_ID = 'find-views-by-id'
  3460. FIND_VIEWS_WITH_TEXT = 'find-views-with-text'
  3461. FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 'find-views-with-content-description'
  3462. USE_REGEXPS = 'use-regexps'
  3463. VERBOSE_COMMENTS = 'verbose-comments'
  3464. UNIT_TEST_CLASS = 'unit-test-class'
  3465. UNIT_TEST_METHOD = 'unit-test-method'
  3466. USE_JAR = 'use-jar'
  3467. USE_DICTIONARY = 'use-dictionary'
  3468. DICTIONARY_KEYS_FROM = 'dictionary-keys-from'
  3469. AUTO_REGEXPS = 'auto-regexps'
  3470. START_ACTIVITY = 'start-activity'
  3471. OUTPUT = 'output'
  3472. INTERACTIVE = 'interactive'
  3473. WINDOW = 'window'
  3474. APPEND_TO_SYS_PATH = 'append-to-sys-path'
  3475. PREPEND_TO_SYS_PATH = 'prepend-to-sys-path'
  3476. SAVE_SCREENSHOT = 'save-screenshot'
  3477. SAVE_VIEW_SCREENSHOTS = 'save-view-screenshots'
  3478. GUI = 'gui'
  3479. SCALE = 'scale'
  3480. DO_NOT_VERIFY_SCREEN_DUMP = 'do-not-verify-screen-dump'
  3481. ORIENTATION_LOCKED = 'orientation-locked'
  3482. SERIALNO = 'serialno'
  3483. MULTI_DEVICE = 'multi-device'
  3484. LOG_ACTIONS = 'log-actions'
  3485. DEVICE_ART = 'device-art'
  3486. DROP_SHADOW = 'drop-shadow'
  3487. SCREEN_GLARE = 'glare'
  3488. SHORT_OPTS = 'HVvIEFSkw:i:t:d:rCUM:j:D:K:R:a:o:pf:W:GuP:Os:mLA:ZB'
  3489. LONG_OPTS = [HELP, VERBOSE, VERSION, IGNORE_SECURE_DEVICE, IGNORE_VERSION_CHECK, FORCE_VIEW_SERVER_USE,
  3490. DO_NOT_START_VIEW_SERVER,
  3491. DO_NOT_IGNORE_UIAUTOMATOR_KILLED,
  3492. WINDOW + '=',
  3493. FIND_VIEWS_BY_ID + '=', FIND_VIEWS_WITH_TEXT + '=', FIND_VIEWS_WITH_CONTENT_DESCRIPTION + '=',
  3494. USE_REGEXPS, VERBOSE_COMMENTS, UNIT_TEST_CLASS, UNIT_TEST_METHOD + '=',
  3495. USE_JAR + '=', USE_DICTIONARY + '=', DICTIONARY_KEYS_FROM + '=', AUTO_REGEXPS + '=',
  3496. START_ACTIVITY + '=',
  3497. OUTPUT + '=', PREPEND_TO_SYS_PATH,
  3498. SAVE_SCREENSHOT + '=', SAVE_VIEW_SCREENSHOTS + '=',
  3499. GUI,
  3500. DO_NOT_VERIFY_SCREEN_DUMP,
  3501. SCALE + '=',
  3502. ORIENTATION_LOCKED,
  3503. SERIALNO + '=',
  3504. MULTI_DEVICE,
  3505. LOG_ACTIONS,
  3506. DEVICE_ART + '=', DROP_SHADOW, SCREEN_GLARE,
  3507. ]
  3508. LONG_OPTS_ARG = {WINDOW: 'WINDOW',
  3509. FIND_VIEWS_BY_ID: 'BOOL', FIND_VIEWS_WITH_TEXT: 'BOOL', FIND_VIEWS_WITH_CONTENT_DESCRIPTION: 'BOOL',
  3510. USE_JAR: 'BOOL', USE_DICTIONARY: 'BOOL', DICTIONARY_KEYS_FROM: 'VALUE', AUTO_REGEXPS: 'LIST',
  3511. START_ACTIVITY: 'COMPONENT',
  3512. OUTPUT: 'FILENAME',
  3513. SAVE_SCREENSHOT: 'FILENAME', SAVE_VIEW_SCREENSHOTS: 'DIR',
  3514. UNIT_TEST_METHOD: 'NAME',
  3515. SCALE: 'FLOAT',
  3516. SERIALNO: 'LIST',
  3517. DEVICE_ART: 'MODEL'}
  3518. OPTS_HELP = {
  3519. 'H': 'prints this help',
  3520. 'V': 'verbose comments',
  3521. 'v': 'prints version number and exists',
  3522. 'k': 'don\'t ignore UiAutomator killed',
  3523. 'w': 'use WINDOW content (default: -1, all windows)',
  3524. 'i': 'whether to use findViewById() in script',
  3525. 't': 'whether to use findViewWithText() in script',
  3526. 'd': 'whether to use findViewWithContentDescription',
  3527. 'r': 'use regexps in matches',
  3528. 'U': 'generates unit test class and script',
  3529. 'M': 'generates unit test method. Can be used with or without -U',
  3530. 'j': 'use jar and appropriate shebang to run script (deprecated)',
  3531. 'D': 'use a dictionary to store the Views found',
  3532. 'K': 'dictionary keys from: id, text, content-description',
  3533. 'R': 'auto regexps (i.e. clock), implies -r. help list options',
  3534. 'a': 'starts Activity before dump',
  3535. 'o': 'output filename',
  3536. 'p': 'prepend environment variables values to sys.path',
  3537. 'f': 'save screenshot to file',
  3538. 'W': 'save View screenshots to files in directory',
  3539. 'E': 'ignores ADB version check',
  3540. 'G': 'presents the GUI (EXPERIMENTAL)',
  3541. 'P': 'scale percentage (i.e. 0.5)',
  3542. 'u': 'do not verify screen state after dump',
  3543. 'O': 'orientation locked in generated test',
  3544. 's': 'device serial number (can be more than 1)',
  3545. 'm': 'enables multi-device test generation',
  3546. 'L': 'log actions using logcat',
  3547. 'A': 'device art model to frame screenshot (auto: autodetected)',
  3548. 'Z': 'drop shadow for device art screenshot',
  3549. 'B': 'screen glare over screenshot',
  3550. }
  3551. class CulebraTestCase(unittest.TestCase):
  3552. '''
  3553. The base class for all CulebraTests.
  3554. Class variables
  3555. ---------------
  3556. There are some class variables that can be used to change the behavior of the tests.
  3557. B{serialno}: The serial number of the device. This can also be a list of devices for I{mutli-devices}
  3558. tests or the keyword C{all} to run the tests on all available devices or C{default} to run the tests
  3559. only on the default (first) device.
  3560. When a I{multi-device} test is running the available devices are available in a list named
  3561. L{self.devices} which has the corresponding L{ConnectedDevices} entries.
  3562. Also, in the case of I{multi-devices} tests and to be backward compatible with I{single-device} tests
  3563. the default device, the first one in the devices list, is assigned to L{self.device}, L{self.vc} and
  3564. L{self.serialno} too.
  3565. B{verbose}: The verbosity of the tests. This can be changed from the test command line using the
  3566. command line option C{-v} or C{--verbose}.
  3567. '''
  3568. kwargs1 = None
  3569. kwargs2 = None
  3570. devices = None
  3571. ''' The list of connected devices '''
  3572. defaultDevice = None
  3573. ''' The default L{ConnectedDevice}. Set to the first one found for multi-device cases '''
  3574. serialno = None
  3575. ''' The default connected device C{serialno} '''
  3576. device = None
  3577. ''' The default connected device '''
  3578. vc = None
  3579. ''' The default connected device C{ViewClient} '''
  3580. verbose = False
  3581. options = {}
  3582. @classmethod
  3583. def setUpClass(cls):
  3584. cls.kwargs1 = {'ignoreversioncheck': False, 'verbose': False, 'ignoresecuredevice': False}
  3585. cls.kwargs2 = {'startviewserver': True, 'forceviewserveruse': False, 'autodump': False, 'ignoreuiautomatorkilled': True}
  3586. def __init__(self, methodName='runTest'):
  3587. self.Log = CulebraTestCase.__Log(self)
  3588. unittest.TestCase.__init__(self, methodName=methodName)
  3589. def setUp(self):
  3590. __devices = None
  3591. if self.serialno:
  3592. # serialno can be 1 serialno, multiple serialnos, 'all' or 'default'
  3593. if self.serialno.lower() == 'all':
  3594. __devices = [d.serialno for d in adbclient.AdbClient().getDevices()]
  3595. elif self.serialno.lower() == 'default':
  3596. __devices = [adbclient.AdbClient().getDevices()[0].serialno]
  3597. else:
  3598. __devices = self.serialno.split()
  3599. if len(__devices) > 1:
  3600. self.devices = __devices
  3601. # FIXME: both cases should be unified
  3602. if self.devices:
  3603. __devices = self.devices
  3604. self.devices = []
  3605. for serialno in __devices:
  3606. device, serialno = ViewClient.connectToDeviceOrExit(serialno=serialno, **self.kwargs1)
  3607. if self.options[CulebraOptions.START_ACTIVITY]:
  3608. device.startActivity(component=self.options[CulebraOptions.START_ACTIVITY])
  3609. vc = ViewClient(device, serialno, **self.kwargs2)
  3610. self.devices.append(ConnectedDevice(serialno=serialno, device=device, vc=vc))
  3611. # Select the first devices as default
  3612. self.defaultDevice = self.devices[0]
  3613. self.device = self.defaultDevice.device
  3614. self.serialno = self.defaultDevice.serialno
  3615. self.vc = self.defaultDevice.vc
  3616. else:
  3617. self.devices = []
  3618. if __devices:
  3619. # A list containing only one device was specified
  3620. self.serialno = __devices[0]
  3621. self.device, self.serialno = ViewClient.connectToDeviceOrExit(serialno=self.serialno, **self.kwargs1)
  3622. if self.options[CulebraOptions.START_ACTIVITY]:
  3623. self.device.startActivity(component=self.options[CulebraOptions.START_ACTIVITY])
  3624. self.vc = ViewClient(self.device, self.serialno, **self.kwargs2)
  3625. # Set the default device, to be consistent with multi-devices case
  3626. self.devices.append(ConnectedDevice(serialno=self.serialno, device=self.device, vc=self.vc))
  3627. def tearDown(self):
  3628. pass
  3629. def preconditions(self):
  3630. if self.options[CulebraOptions.ORIENTATION_LOCKED] is not None:
  3631. # If orientation locked was set to a valid orientation value then use it to compare
  3632. # against current orientation (when the test is run)
  3633. return (self.device.display['orientation'] == self.options[CulebraOptions.ORIENTATION_LOCKED])
  3634. return True
  3635. def isTestRunningOnMultipleDevices(self):
  3636. return (len(self.devices) > 1)
  3637. @staticmethod
  3638. def __passAll(arg):
  3639. return True
  3640. def all(self, arg, _filter=None):
  3641. # CulebraTestCase.__passAll cannot be specified as the default argument value
  3642. if _filter is None:
  3643. _filter = CulebraTestCase.__passAll
  3644. if DEBUG_MULTI:
  3645. print >> sys.stderr, "all(%s, %s)" % (arg, _filter)
  3646. l = (getattr(d, arg) for d in self.devices)
  3647. for i in l:
  3648. print >> sys.stderr, " i=", i
  3649. return filter(_filter, (getattr(d, arg) for d in self.devices))
  3650. def allVcs(self, _filter=None):
  3651. return self.all('vc', _filter)
  3652. def allDevices(self, _filter=None):
  3653. return self.all('device', _filter)
  3654. def allSerialnos(self, _filter=None):
  3655. return self.all('serialno', _filter)
  3656. def log(self, message, priority='D'):
  3657. '''
  3658. Logs a message with the specified priority.
  3659. '''
  3660. self.device.log('CULEBRA', message, priority, CulebraTestCase.verbose)
  3661. class __Log():
  3662. '''
  3663. Log class to simulate C{android.util.Log}
  3664. '''
  3665. def __init__(self, culebraTestCase):
  3666. self.culebraTestCase = culebraTestCase
  3667. def __getattr__(self, attr):
  3668. '''
  3669. Returns the corresponding log method or @C{AttributeError}.
  3670. '''
  3671. if attr in ['v', 'd', 'i', 'w', 'e']:
  3672. return lambda message: self.culebraTestCase.log(message, priority=attr.upper())
  3673. raise AttributeError(self.__class__.__name__ + ' has no attribute "%s"' % attr)
  3674. @staticmethod
  3675. def main():
  3676. # If you want to specify tests classes and methods in the command line you will be forced
  3677. # to include -s or --serialno and the serial number of the device (could be a regexp)
  3678. # as ViewClient would have no way of determine what it is.
  3679. # This could be also a list of devices (delimited by whitespaces) and in such case all of
  3680. # them will be used.
  3681. # The special argument 'all' means all the connected devices.
  3682. ser = ['-s', '--serialno']
  3683. old = '%(failfast)'
  3684. new = ' %s s The serial number[s] to connect to or \'all\'\n%s' % (', '.join(ser), old)
  3685. unittest.TestProgram.USAGE = unittest.TestProgram.USAGE.replace(old, new)
  3686. argsToRemove = []
  3687. i = 0
  3688. while i < len(sys.argv):
  3689. a = sys.argv[i]
  3690. if a in ['-v', '--verbose']:
  3691. # make CulebraTestCase.verbose the same as unittest verbose
  3692. CulebraTestCase.verbose = True
  3693. elif a in ser:
  3694. # remove arguments not handled by unittest
  3695. if len(sys.argv) > (i+1):
  3696. argsToRemove.append(sys.argv[i])
  3697. CulebraTestCase.serialno = sys.argv[i+1]
  3698. argsToRemove.append(CulebraTestCase.serialno)
  3699. i += 1
  3700. else:
  3701. raise RuntimeError('serial number missing')
  3702. i += 1
  3703. for a in argsToRemove:
  3704. sys.argv.remove(a)
  3705. unittest.main()
  3706. if __name__ == "__main__":
  3707. try:
  3708. vc = ViewClient(None)
  3709. except:
  3710. print "%s: Don't expect this to do anything" % __file__