12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205 |
- # -*- coding: utf-8 -*-
- '''
- Copyright (C) 2012-2015 Diego Torres Milano
- Created on Feb 2, 2012
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- @author: Diego Torres Milano
- '''
- __version__ = '10.3.1'
- import sys
- import warnings
- if sys.executable:
- if 'monkeyrunner' in sys.executable:
- warnings.warn(
- '''
- You should use a 'python' interpreter, not 'monkeyrunner' for this module
- ''', RuntimeWarning)
- import subprocess
- import re
- import socket
- import os
- import types
- import time
- import signal
- import copy
- import pickle
- import platform
- import xml.parsers.expat
- import unittest
- from com.dtmilano.android.common import _nd, _nh, _ns, obtainPxPy, obtainVxVy,\
- obtainVwVh, obtainAdbPath
- from com.dtmilano.android.window import Window
- from com.dtmilano.android.adb import adbclient
- DEBUG = False
- DEBUG_DEVICE = DEBUG and False
- DEBUG_RECEIVED = DEBUG and False
- DEBUG_TREE = DEBUG and False
- DEBUG_GETATTR = DEBUG and False
- DEBUG_CALL = DEBUG and False
- DEBUG_COORDS = DEBUG and False
- DEBUG_TOUCH = DEBUG and False
- DEBUG_STATUSBAR = DEBUG and False
- DEBUG_WINDOWS = DEBUG and False
- DEBUG_BOUNDS = DEBUG and False
- DEBUG_DISTANCE = DEBUG and False
- DEBUG_MULTI = DEBUG and False
- DEBUG_VIEW = DEBUG and False
- DEBUG_VIEW_FACTORY = DEBUG and False
- WARNINGS = False
- VIEW_SERVER_HOST = 'localhost'
- VIEW_SERVER_PORT = 4939
- ADB_DEFAULT_PORT = 5555
- OFFSET = 25
- ''' This assumes the smallest touchable view on the screen is approximately 50px x 50px
- and touches it at M{(x+OFFSET, y+OFFSET)} '''
- USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES = True
- ''' Use C{AdbClient} to obtain the needed properties. If this is
- C{False} then C{adb shell getprop} is used '''
- USE_PHYSICAL_DISPLAY_INFO = True
- ''' Use C{dumpsys display} to obtain display properties. If this is
- C{False} then C{USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES} is used '''
- SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED = False
- ''' Skips some classes related with the Action Bar and the PhoneWindow$DecorView in the
- coordinates calculation
- @see: L{View.getXY()} '''
- VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED = False
- ''' Under some conditions the touch event should be longer [t(DOWN) << t(UP)]. C{True} enables a
- workaround to delay the events.'''
- # some device properties
- VERSION_SDK_PROPERTY = 'ro.build.version.sdk'
- VERSION_RELEASE_PROPERTY = 'ro.build.version.release'
- # some constants for the attributes
- ID_PROPERTY = 'mID'
- ID_PROPERTY_UI_AUTOMATOR = 'uniqueId'
- TEXT_PROPERTY = 'text:mText'
- TEXT_PROPERTY_API_10 = 'mText'
- TEXT_PROPERTY_UI_AUTOMATOR = 'text'
- WS = u"\xfe" # the whitespace replacement char for TEXT_PROPERTY
- TAG_PROPERTY = 'getTag()'
- LEFT_PROPERTY = 'layout:mLeft'
- LEFT_PROPERTY_API_8 = 'mLeft'
- TOP_PROPERTY = 'layout:mTop'
- TOP_PROPERTY_API_8 = 'mTop'
- WIDTH_PROPERTY = 'layout:getWidth()'
- WIDTH_PROPERTY_API_8 = 'getWidth()'
- HEIGHT_PROPERTY = 'layout:getHeight()'
- HEIGHT_PROPERTY_API_8 = 'getHeight()'
- GET_VISIBILITY_PROPERTY = 'getVisibility()'
- LAYOUT_TOP_MARGIN_PROPERTY = 'layout:layout_topMargin'
- IS_FOCUSED_PROPERTY_UI_AUTOMATOR = 'focused'
- IS_FOCUSED_PROPERTY = 'focus:isFocused()'
- # visibility
- VISIBLE = 0x0
- INVISIBLE = 0x4
- GONE = 0x8
- RegexType = type(re.compile(''))
- IP_RE = re.compile('^(\d{1,3}\.){3}\d{1,3}$')
- ID_RE = re.compile('id/([^/]*)(/(\d+))?')
- class ViewNotFoundException(Exception):
- '''
- ViewNotFoundException is raised when a View is not found.
- '''
- def __init__(self, attr, value, root):
- if isinstance(value, RegexType):
- msg = "Couldn't find View with %s that matches '%s' in tree with root=%s" % (attr, value.pattern, root)
- else:
- msg = "Couldn't find View with %s='%s' in tree with root=%s" % (attr, value, root)
- super(Exception, self).__init__(msg)
- class View:
- '''
- View class
- '''
- @staticmethod
- def factory(arg1, arg2, version=-1, forceviewserveruse=False, windowId=None):
- '''
- View factory
- @type arg1: ClassType or dict
- @type arg2: View instance or AdbClient
- '''
- if DEBUG_VIEW_FACTORY:
- print >> sys.stderr, "View.factory(%s, %s, %s, %s)" % (arg1, arg2, version, forceviewserveruse)
- if type(arg1) == types.ClassType:
- cls = arg1
- attrs = None
- else:
- cls = None
- attrs = arg1
- if isinstance(arg2, View):
- view = arg2
- device = None
- else:
- device = arg2
- view = None
- if attrs and attrs.has_key('class'):
- clazz = attrs['class']
- if DEBUG_VIEW_FACTORY:
- print >> sys.stderr, " View.factory: creating View with specific class: %s" % clazz
- if clazz == 'android.widget.TextView':
- return TextView(attrs, device, version, forceviewserveruse, windowId)
- elif clazz == 'android.widget.EditText':
- return EditText(attrs, device, version, forceviewserveruse, windowId)
- elif clazz == 'android.widget.ListView':
- return ListView(attrs, device, version, forceviewserveruse, windowId)
- else:
- return View(attrs, device, version, forceviewserveruse, windowId)
- elif cls:
- if view:
- return cls.__copy(view)
- else:
- return cls(attrs, device, version, forceviewserveruse, windowId)
- elif view:
- return copy.copy(view)
- else:
- if DEBUG_VIEW_FACTORY:
- print >> sys.stderr, " View.factory: creating generic View"
- return View(attrs, device, version, forceviewserveruse, windowId)
- @classmethod
- def __copy(cls, view):
- '''
- Copy constructor
- '''
- return cls(view.map, view.device, view.version, view.forceviewserveruse, view.windowId)
- def __init__(self, _map, device, version=-1, forceviewserveruse=False, windowId=None):
- '''
- Constructor
- @type _map: map
- @param _map: the map containing the (attribute, value) pairs
- @type device: AdbClient
- @param device: the device containing this View
- @type version: int
- @param version: the Android SDK version number of the platform where this View belongs. If
- this is C{-1} then the Android SDK version will be obtained in this
- constructor.
- @type forceviewserveruse: boolean
- @param forceviewserveruse: Force the use of C{ViewServer} even if the conditions were given
- to use C{UiAutomator}.
- '''
- if DEBUG_VIEW:
- print >> sys.stderr, "View.__init__(%s, %s, %s, %s)" % ("map" if _map is not None else None, device, version, forceviewserveruse)
- if _map:
- print >> sys.stderr, " map:", type(_map)
- for attr, val in _map.iteritems():
- if len(val) > 50:
- val = val[:50] + "..."
- print >> sys.stderr, " %s=%s" % (attr, val)
- self.map = _map
- ''' The map that contains the C{attr},C{value} pairs '''
- self.device = device
- ''' The AdbClient '''
- self.children = []
- ''' The children of this View '''
- self.parent = None
- ''' The parent of this View '''
- self.windows = {}
- self.currentFocus = None
- ''' The current focus '''
- self.windowId = windowId
- ''' The window this view resides '''
- self.build = {}
- ''' Build properties '''
- self.version = version
- ''' API version number '''
- self.forceviewserveruse = forceviewserveruse
- ''' Force ViewServer use '''
- self.uiScrollable = None
- ''' If this is a scrollable View this keeps the L{UiScrollable} object '''
- self.target = False
- ''' Is this a touch target zone '''
- if version != -1:
- self.build[VERSION_SDK_PROPERTY] = version
- else:
- try:
- if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES:
- self.build[VERSION_SDK_PROPERTY] = int(device.getProperty(VERSION_SDK_PROPERTY))
- else:
- self.build[VERSION_SDK_PROPERTY] = int(device.shell('getprop ' + VERSION_SDK_PROPERTY)[:-2])
- except:
- self.build[VERSION_SDK_PROPERTY] = -1
- version = self.build[VERSION_SDK_PROPERTY]
- self.useUiAutomator = (version >= 16) and not forceviewserveruse
- ''' Whether to use UIAutomator or ViewServer '''
- self.idProperty = None
- ''' The id property depending on the View attribute format '''
- self.textProperty = None
- ''' The text property depending on the View attribute format '''
- self.tagProperty = None
- ''' The tag property depending on the View attribute format '''
- self.leftProperty = None
- ''' The left property depending on the View attribute format '''
- self.topProperty = None
- ''' The top property depending on the View attribute format '''
- self.widthProperty = None
- ''' The width property depending on the View attribute format '''
- self.heightProperty = None
- ''' The height property depending on the View attribute format '''
- self.isFocusedProperty = None
- ''' The focused property depending on the View attribute format '''
- if version >= 16 and self.useUiAutomator:
- self.idProperty = ID_PROPERTY_UI_AUTOMATOR
- self.textProperty = TEXT_PROPERTY_UI_AUTOMATOR
- self.leftProperty = LEFT_PROPERTY
- self.topProperty = TOP_PROPERTY
- self.widthProperty = WIDTH_PROPERTY
- self.heightProperty = HEIGHT_PROPERTY
- self.isFocusedProperty = IS_FOCUSED_PROPERTY_UI_AUTOMATOR
- elif version > 10 and (version < 16 or self.useUiAutomator):
- self.idProperty = ID_PROPERTY
- self.textProperty = TEXT_PROPERTY
- self.tagProperty = TAG_PROPERTY
- self.leftProperty = LEFT_PROPERTY
- self.topProperty = TOP_PROPERTY
- self.widthProperty = WIDTH_PROPERTY
- self.heightProperty = HEIGHT_PROPERTY
- self.isFocusedProperty = IS_FOCUSED_PROPERTY
- elif version == 10:
- self.idProperty = ID_PROPERTY
- self.textProperty = TEXT_PROPERTY_API_10
- self.tagProperty = TAG_PROPERTY
- self.leftProperty = LEFT_PROPERTY
- self.topProperty = TOP_PROPERTY
- self.widthProperty = WIDTH_PROPERTY
- self.heightProperty = HEIGHT_PROPERTY
- self.isFocusedProperty = IS_FOCUSED_PROPERTY
- elif version >= 7 and version < 10:
- self.idProperty = ID_PROPERTY
- self.textProperty = TEXT_PROPERTY_API_10
- self.tagProperty = TAG_PROPERTY
- self.leftProperty = LEFT_PROPERTY_API_8
- self.topProperty = TOP_PROPERTY_API_8
- self.widthProperty = WIDTH_PROPERTY_API_8
- self.heightProperty = HEIGHT_PROPERTY_API_8
- self.isFocusedProperty = IS_FOCUSED_PROPERTY
- elif version > 0 and version < 7:
- self.idProperty = ID_PROPERTY
- self.textProperty = TEXT_PROPERTY_API_10
- self.tagProperty = TAG_PROPERTY
- self.leftProperty = LEFT_PROPERTY
- self.topProperty = TOP_PROPERTY
- self.widthProperty = WIDTH_PROPERTY
- self.heightProperty = HEIGHT_PROPERTY
- self.isFocusedProperty = IS_FOCUSED_PROPERTY
- elif version == -1:
- self.idProperty = ID_PROPERTY
- self.textProperty = TEXT_PROPERTY
- self.tagProperty = TAG_PROPERTY
- self.leftProperty = LEFT_PROPERTY
- self.topProperty = TOP_PROPERTY
- self.widthProperty = WIDTH_PROPERTY
- self.heightProperty = HEIGHT_PROPERTY
- self.isFocusedProperty = IS_FOCUSED_PROPERTY
- else:
- self.idProperty = ID_PROPERTY
- self.textProperty = TEXT_PROPERTY
- self.tagProperty = TAG_PROPERTY
- self.leftProperty = LEFT_PROPERTY
- self.topProperty = TOP_PROPERTY
- self.widthProperty = WIDTH_PROPERTY
- self.heightProperty = HEIGHT_PROPERTY
- self.isFocusedProperty = IS_FOCUSED_PROPERTY
-
- try:
- if self.isScrollable():
- self.uiScrollable = UiScrollable(self)
- except AttributeError:
- pass
- def __getitem__(self, key):
- return self.map[key]
- def __getattr__(self, name):
- if DEBUG_GETATTR:
- print >>sys.stderr, "__getattr__(%s) version: %d" % (name, self.build[VERSION_SDK_PROPERTY])
- # NOTE:
- # I should try to see if 'name' is a defined method
- # but it seems that if I call locals() here an infinite loop is entered
- if self.map.has_key(name):
- r = self.map[name]
- elif self.map.has_key(name + '()'):
- # the method names are stored in the map with their trailing '()'
- r = self.map[name + '()']
- elif name.count("_") > 0:
- mangledList = self.allPossibleNamesWithColon(name)
- mangledName = self.intersection(mangledList, self.map.keys())
- if len(mangledName) > 0 and self.map.has_key(mangledName[0]):
- r = self.map[mangledName[0]]
- else:
- # Default behavior
- raise AttributeError, name
- elif name.startswith('is'):
- # try removing 'is' prefix
- if DEBUG_GETATTR:
- print >> sys.stderr, " __getattr__: trying without 'is' prefix"
- suffix = name[2:].lower()
- if self.map.has_key(suffix):
- r = self.map[suffix]
- else:
- # Default behavior
- raise AttributeError, name
- elif name.startswith('get'):
- # try removing 'get' prefix
- if DEBUG_GETATTR:
- print >> sys.stderr, " __getattr__: trying without 'get' prefix"
- suffix = name[3:].lower()
- if self.map.has_key(suffix):
- r = self.map[suffix]
- else:
- # Default behavior
- raise AttributeError, name
- elif name == 'getResourceId':
- if DEBUG_GETATTR:
- print >> sys.stderr, " __getattr__: getResourceId"
- if self.map.has_key('resource-id'):
- r = self.map['resource-id']
- else:
- # Default behavior
- raise AttributeError, name
- else:
- # Default behavior
- raise AttributeError, name
-
- # if the method name starts with 'is' let's assume its return value is boolean
- # if name[:2] == 'is':
- # r = True if r == 'true' else False
- if r == 'true':
- r = True
- elif r == 'false':
- r = False
- # this should not cached in some way
- def innerMethod():
- if DEBUG_GETATTR:
- print >>sys.stderr, "innerMethod: %s returning %s" % (innerMethod.__name__, r)
- return r
- innerMethod.__name__ = name
- # this should work, but then there's problems with the arguments of innerMethod
- # even if innerMethod(self) is added
- #setattr(View, innerMethod.__name__, innerMethod)
- #setattr(self, innerMethod.__name__, innerMethod)
- return innerMethod
- def __call__(self, *args, **kwargs):
- if DEBUG_CALL:
- print >>sys.stderr, "__call__(%s)" % (args if args else None)
- def getClass(self):
- '''
- Gets the L{View} class
- @return: the L{View} class or C{None} if not defined
- '''
- try:
- return self.map['class']
- except:
- return None
- def getId(self):
- '''
- Gets the L{View} Id
- @return: the L{View} C{Id} or C{None} if not defined
- @see: L{getUniqueId()}
- '''
- try:
- return self.map['resource-id']
- except:
- pass
- try:
- return self.map[self.idProperty]
- except:
- return None
- def getContentDescription(self):
- '''
- Gets the content description.
- '''
- try:
- return self.map['content-desc']
- except:
- return None
- def getTag(self):
- '''
- Gets the tag.
- '''
- try:
- return self.map[self.tagProperty]
- except:
- return None
- def getParent(self):
- '''
- Gets the parent.
- '''
- return self.parent
- def getChildren(self):
- '''
- Gets the children of this L{View}.
- '''
-
- return self.children
-
- def getText(self):
- '''
- Gets the text attribute.
- @return: the text attribute or C{None} if not defined
- '''
- try:
- return self.map[self.textProperty]
- except Exception:
- return None
- def getHeight(self):
- '''
- Gets the height.
- '''
- if self.useUiAutomator:
- return self.map['bounds'][1][1] - self.map['bounds'][0][1]
- else:
- try:
- return int(self.map[self.heightProperty])
- except:
- return 0
- def getWidth(self):
- '''
- Gets the width.
- '''
- if self.useUiAutomator:
- return self.map['bounds'][1][0] - self.map['bounds'][0][0]
- else:
- try:
- return int(self.map[self.widthProperty])
- except:
- return 0
- def getUniqueId(self):
- '''
- Gets the unique Id of this View.
- @see: L{ViewClient.__splitAttrs()} for a discussion on B{Unique Ids}
- '''
- try:
- return self.map['uniqueId']
- except:
- return None
- def getVisibility(self):
- '''
- Gets the View visibility
- '''
- try:
- if self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
- return VISIBLE
- elif self.map[GET_VISIBILITY_PROPERTY] == 'INVISIBLE':
- return INVISIBLE
- elif self.map[GET_VISIBILITY_PROPERTY] == 'GONE':
- return GONE
- else:
- return -2
- except:
- return -1
- def getX(self):
- '''
- Gets the View X coordinate
- '''
- return self.getXY()[0]
- def __getX(self):
- '''
- Gets the View X coordinate
- '''
- if DEBUG_COORDS:
- print >>sys.stderr, "getX(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
- x = 0
- if self.useUiAutomator:
- x = self.map['bounds'][0][0]
- else:
- try:
- if GET_VISIBILITY_PROPERTY in self.map and self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
- _x = int(self.map[self.leftProperty])
- if DEBUG_COORDS: print >>sys.stderr, " getX: VISIBLE adding %d" % _x
- x += _x
- except:
- warnings.warn("View %s has no '%s' property" % (self.getId(), self.leftProperty))
- if DEBUG_COORDS: print >>sys.stderr, " getX: returning %d" % (x)
- return x
- def getY(self):
- '''
- Gets the View Y coordinate
- '''
- return self.getXY()[1]
- def __getY(self):
- '''
- Gets the View Y coordinate
- '''
- if DEBUG_COORDS:
- print >>sys.stderr, "getY(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
- y = 0
- if self.useUiAutomator:
- y = self.map['bounds'][0][1]
- else:
- try:
- if GET_VISIBILITY_PROPERTY in self.map and self.map[GET_VISIBILITY_PROPERTY] == 'VISIBLE':
- _y = int(self.map[self.topProperty])
- if DEBUG_COORDS: print >>sys.stderr, " getY: VISIBLE adding %d" % _y
- y += _y
- except:
- warnings.warn("View %s has no '%s' property" % (self.getId(), self.topProperty))
- if DEBUG_COORDS: print >>sys.stderr, " getY: returning %d" % (y)
- return y
- def getXY(self, debug=False):
- '''
- Returns the I{screen} coordinates of this C{View}.
- WARNING: Don't call self.getX() or self.getY() inside this method
- or it will enter an infinite loop
- @return: The I{screen} coordinates of this C{View}
- '''
- if DEBUG_COORDS or debug:
- try:
- _id = self.getId()
- except:
- _id = "NO_ID"
- print >> sys.stderr, "getXY(%s %s ## %s)" % (self.getClass(), _id, self.getUniqueId())
- x = self.__getX()
- y = self.__getY()
- if self.useUiAutomator:
- return (x, y)
- parent = self.parent
- if DEBUG_COORDS: print >> sys.stderr, " getXY: x=%s y=%s parent=%s" % (x, y, parent.getUniqueId() if parent else "None")
- hx = 0
- ''' Hierarchy accumulated X '''
- hy = 0
- ''' Hierarchy accumulated Y '''
- if DEBUG_COORDS: print >> sys.stderr, " getXY: not using UiAutomator, calculating parent coordinates"
- while parent != None:
- if DEBUG_COORDS: print >> sys.stderr, " getXY: parent: %s %s <<<<" % (parent.getClass(), parent.getId())
- if SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED:
- if parent.getClass() in [ 'com.android.internal.widget.ActionBarView',
- 'com.android.internal.widget.ActionBarContextView',
- 'com.android.internal.view.menu.ActionMenuView',
- 'com.android.internal.policy.impl.PhoneWindow$DecorView' ]:
- if DEBUG_COORDS: print >> sys.stderr, " getXY: skipping %s %s (%d,%d)" % (parent.getClass(), parent.getId(), parent.__getX(), parent.__getY())
- parent = parent.parent
- continue
- if DEBUG_COORDS: print >> sys.stderr, " getXY: parent=%s x=%d hx=%d y=%d hy=%d" % (parent.getId(), x, hx, y, hy)
- hx += parent.__getX()
- hy += parent.__getY()
- parent = parent.parent
- (wvx, wvy) = self.__dumpWindowsInformation(debug=debug)
- if DEBUG_COORDS or debug:
- print >>sys.stderr, " getXY: wv=(%d, %d) (windows information)" % (wvx, wvy)
- try:
- if self.windowId:
- fw = self.windows[self.windowId]
- else:
- fw = self.windows[self.currentFocus]
- if DEBUG_STATUSBAR:
- print >> sys.stderr, " getXY: focused window=", fw
- 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)
- except KeyError:
- fw = None
- (sbw, sbh) = self.__obtainStatusBarDimensionsIfVisible()
- if DEBUG_COORDS or debug:
- print >>sys.stderr, " getXY: sb=(%d, %d) (statusbar dimensions)" % (sbw, sbh)
- statusBarOffset = 0
- pwx = 0
- pwy = 0
- if fw:
- if DEBUG_COORDS:
- print >>sys.stderr, " getXY: focused window=", fw, "sb=", (sbw, sbh)
- if fw.wvy <= sbh: # it's very unlikely that fw.wvy < sbh, that is a window over the statusbar
- if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: yes, considering offset=", sbh
- statusBarOffset = sbh
- else:
- if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: no, ignoring statusbar offset fw.wvy=", fw.wvy, ">", sbh
- if fw.py == fw.wvy:
- if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: but wait, fw.py == fw.wvy so we are adjusting by ", (fw.px, fw.py)
- pwx = fw.px
- pwy = fw.py
- else:
- if DEBUG_STATUSBAR: print >>sys.stderr, " getXY: fw.py=%d <= fw.wvy=%d, no adjustment" % (fw.py, fw.wvy)
- if DEBUG_COORDS or DEBUG_STATUSBAR or debug:
- print >>sys.stderr, " getXY: returning (%d, %d) ***" % (x+hx+wvx+pwx, y+hy+wvy-statusBarOffset+pwy)
- print >>sys.stderr, " x=%d+%d+%d+%d" % (x,hx,wvx,pwx)
- print >>sys.stderr, " y=%d+%d+%d-%d+%d" % (y,hy,wvy,statusBarOffset,pwy)
- return (x+hx+wvx+pwx, y+hy+wvy-statusBarOffset+pwy)
- def getCoords(self):
- '''
- Gets the coords of the View
- @return: A tuple containing the View's coordinates ((L, T), (R, B))
- '''
- if DEBUG_COORDS:
- print >>sys.stderr, "getCoords(%s %s ## %s)" % (self.getClass(), self.getId(), self.getUniqueId())
- (x, y) = self.getXY();
- w = self.getWidth()
- h = self.getHeight()
- return ((x, y), (x+w, y+h))
- def getPositionAndSize(self):
- '''
- Gets the position and size (X,Y, W, H)
- @return: A tuple containing the View's coordinates (X, Y, W, H)
- '''
- (x, y) = self.getXY();
- w = self.getWidth()
- h = self.getHeight()
- return (x, y, w, h)
- def getBounds(self):
- '''
- Gets the View bounds
- '''
- if 'bounds' in self.map:
- return self.map['bounds']
- else:
- return self.getCoords()
- def getCenter(self):
- '''
- Gets the center coords of the View
- @author: U{Dean Morin <https://github.com/deanmorin>}
- '''
- (left, top), (right, bottom) = self.getCoords()
- x = left + (right - left) / 2
- y = top + (bottom - top) / 2
- return (x, y)
- def __obtainStatusBarDimensionsIfVisible(self):
- sbw = 0
- sbh = 0
- for winId in self.windows:
- w = self.windows[winId]
- if DEBUG_COORDS: print >> sys.stderr, " __obtainStatusBarDimensionsIfVisible: w=", w, " w.activity=", w.activity, "%%%"
- if w.activity == 'StatusBar':
- if w.wvy == 0 and w.visibility == 0:
- if DEBUG_COORDS: print >> sys.stderr, " __obtainStatusBarDimensionsIfVisible: statusBar=", (w.wvw, w.wvh)
- sbw = w.wvw
- sbh = w.wvh
- break
- return (sbw, sbh)
- def __obtainVxVy(self, m):
- return obtainVxVy(m)
- def __obtainVwVh(self, m):
- return obtainVwVh(m)
- def __obtainPxPy(self, m):
- return obtainPxPy(m)
- def __dumpWindowsInformation(self, debug=False):
- self.windows = {}
- self.currentFocus = None
- dww = self.device.shell('dumpsys window windows')
- if DEBUG_WINDOWS or debug: print >> sys.stderr, dww
- lines = dww.splitlines()
- widRE = re.compile('^ *Window #%s Window{%s (u\d+ )?%s?.*}:' %
- (_nd('num'), _nh('winId'), _ns('activity', greedy=True)))
- currentFocusRE = re.compile('^ mCurrentFocus=Window{%s .*' % _nh('winId'))
- viewVisibilityRE = re.compile(' mViewVisibility=0x%s ' % _nh('visibility'))
- # This is for 4.0.4 API-15
- containingFrameRE = re.compile('^ *mContainingFrame=\[%s,%s\]\[%s,%s\] mParentFrame=\[%s,%s\]\[%s,%s\]' %
- (_nd('cx'), _nd('cy'), _nd('cw'), _nd('ch'), _nd('px'), _nd('py'), _nd('pw'), _nd('ph')))
- contentFrameRE = re.compile('^ *mContentFrame=\[%s,%s\]\[%s,%s\] mVisibleFrame=\[%s,%s\]\[%s,%s\]' %
- (_nd('x'), _nd('y'), _nd('w'), _nd('h'), _nd('vx'), _nd('vy'), _nd('vx1'), _nd('vy1')))
- # This is for 4.1 API-16
- framesRE = re.compile('^ *Frames: containing=\[%s,%s\]\[%s,%s\] parent=\[%s,%s\]\[%s,%s\]' %
- (_nd('cx'), _nd('cy'), _nd('cw'), _nd('ch'), _nd('px'), _nd('py'), _nd('pw'), _nd('ph')))
- contentRE = re.compile('^ *content=\[%s,%s\]\[%s,%s\] visible=\[%s,%s\]\[%s,%s\]' %
- (_nd('x'), _nd('y'), _nd('w'), _nd('h'), _nd('vx'), _nd('vy'), _nd('vx1'), _nd('vy1')))
- policyVisibilityRE = re.compile('mPolicyVisibility=%s ' % _ns('policyVisibility', greedy=True))
- for l in range(len(lines)):
- m = widRE.search(lines[l])
- if m:
- num = int(m.group('num'))
- winId = m.group('winId')
- activity = m.group('activity')
- wvx = 0
- wvy = 0
- wvw = 0
- wvh = 0
- px = 0
- py = 0
- visibility = -1
- policyVisibility = 0x0
- for l2 in range(l+1, len(lines)):
- m = widRE.search(lines[l2])
- if m:
- l += (l2-1)
- break
- m = viewVisibilityRE.search(lines[l2])
- if m:
- visibility = int(m.group('visibility'))
- if DEBUG_COORDS: print >> sys.stderr, "__dumpWindowsInformation: visibility=", visibility
- if self.build[VERSION_SDK_PROPERTY] >= 17:
- m = framesRE.search(lines[l2])
- if m:
- px, py = obtainPxPy(m)
- m = contentRE.search(lines[l2+2])
- if m:
- wvx, wvy = obtainVxVy(m)
- wvw, wvh = obtainVwVh(m)
- elif self.build[VERSION_SDK_PROPERTY] >= 16:
- m = framesRE.search(lines[l2])
- if m:
- px, py = self.__obtainPxPy(m)
- m = contentRE.search(lines[l2+1])
- if m:
- # FIXME: the information provided by 'dumpsys window windows' in 4.2.1 (API 16)
- # when there's a system dialog may not be correct and causes the View coordinates
- # be offset by this amount, see
- # https://github.com/dtmilano/AndroidViewClient/issues/29
- wvx, wvy = self.__obtainVxVy(m)
- wvw, wvh = self.__obtainVwVh(m)
- elif self.build[VERSION_SDK_PROPERTY] == 15:
- m = containingFrameRE.search(lines[l2])
- if m:
- px, py = self.__obtainPxPy(m)
- m = contentFrameRE.search(lines[l2+1])
- if m:
- wvx, wvy = self.__obtainVxVy(m)
- wvw, wvh = self.__obtainVwVh(m)
- elif self.build[VERSION_SDK_PROPERTY] == 10:
- m = containingFrameRE.search(lines[l2])
- if m:
- px, py = self.__obtainPxPy(m)
- m = contentFrameRE.search(lines[l2+1])
- if m:
- wvx, wvy = self.__obtainVxVy(m)
- wvw, wvh = self.__obtainVwVh(m)
- else:
- warnings.warn("Unsupported Android version %d" % self.build[VERSION_SDK_PROPERTY])
- #print >> sys.stderr, "Searching policyVisibility in", lines[l2]
- m = policyVisibilityRE.search(lines[l2])
- if m:
- policyVisibility = 0x0 if m.group('policyVisibility') == 'true' else 0x8
- self.windows[winId] = Window(num, winId, activity, wvx, wvy, wvw, wvh, px, py, visibility + policyVisibility)
- else:
- m = currentFocusRE.search(lines[l])
- if m:
- self.currentFocus = m.group('winId')
- if self.windowId and self.windowId in self.windows and self.windows[self.windowId].visibility == 0:
- w = self.windows[self.windowId]
- return (w.wvx, w.wvy)
- elif self.currentFocus in self.windows and self.windows[self.currentFocus].visibility == 0:
- if DEBUG_COORDS or debug:
- print >> sys.stderr, "__dumpWindowsInformation: focus=", self.currentFocus
- print >> sys.stderr, "__dumpWindowsInformation:", self.windows[self.currentFocus]
- w = self.windows[self.currentFocus]
- return (w.wvx, w.wvy)
- else:
- if DEBUG_COORDS: print >> sys.stderr, "__dumpWindowsInformation: (0,0)"
- return (0,0)
- def touch(self, eventType=adbclient.DOWN_AND_UP, deltaX=0, deltaY=0):
- '''
- Touches the center of this C{View}. The touch can be displaced from the center by
- using C{deltaX} and C{deltaY} values.
-
- @param eventType: The event type
- @type eventType: L{adbclient.DOWN}, L{adbclient.UP} or L{adbclient.DOWN_AND_UP}
- @param deltaX: Displacement from center (X axis)
- @type deltaX: int
- @param deltaY: Displacement from center (Y axis)
- @type deltaY: int
- '''
- (x, y) = self.getCenter()
- if deltaX:
- x += deltaX
- if deltaY:
- y += deltaY
- if DEBUG_TOUCH:
- print >>sys.stderr, "should touch @ (%d, %d)" % (x, y)
- if VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED and eventType == adbclient.DOWN_AND_UP:
- if WARNINGS:
- print >> sys.stderr, "ViewClient: touch workaround enabled"
- self.device.touch(x, y, eventType=adbclient.DOWN)
- time.sleep(50/1000.0)
- self.device.touch(x+10, y+10, eventType=adbclient.UP)
- else:
- self.device.touch(x, y, eventType=eventType)
- def longTouch(self, duration=2000):
- '''
- Long touches this C{View}
- @param duration: duration in ms
- '''
- (x, y) = self.getCenter()
- # FIXME: get orientation
- self.device.longTouch(x, y, duration, orientation=-1)
- def allPossibleNamesWithColon(self, name):
- l = []
- for _ in range(name.count("_")):
- name = name.replace("_", ":", 1)
- l.append(name)
- return l
- def intersection(self, l1, l2):
- return list(set(l1) & set(l2))
- def containsPoint(self, (x, y)):
- (X, Y, W, H) = self.getPositionAndSize()
- return (((x >= X) and (x <= (X+W)) and ((y >= Y) and (y <= (Y+H)))))
- def add(self, child):
- '''
- Adds a child
- @type child: View
- @param child: The child to add
- '''
- child.parent = self
- self.children.append(child)
- def isClickable(self):
- return self.__getattr__('isClickable')()
- def isFocused(self):
- '''
- Gets the focused value
- @return: the focused value. If the property cannot be found returns C{False}
- '''
- try:
- return True if self.map[self.isFocusedProperty].lower() == 'true' else False
- except Exception:
- return False
- def variableNameFromId(self):
- _id = self.getId()
- if _id:
- var = _id.replace('.', '_').replace(':', '___').replace('/', '_')
- else:
- _id = self.getUniqueId()
- m = ID_RE.match(_id)
- if m:
- var = m.group(1)
- if m.group(3):
- var += m.group(3)
- if re.match('^\d', var):
- var = 'id_' + var
- return var
- def setTarget(self, target):
- self.target = target
- def isTarget(self):
- return self.target
- def writeImageToFile(self, filename, _format="PNG"):
- '''
- Write the View image to the specified filename in the specified format.
- @type filename: str
- @param filename: Absolute path and optional filename receiving the image. If this points to
- a directory, then the filename is determined by this View unique ID and
- format extension.
- @type _format: str
- @param _format: Image format (default format is PNG)
- '''
- if not os.path.isabs(filename):
- raise ValueError("writeImageToFile expects an absolute path (fielname='%s')" % filename)
- if os.path.isdir(filename):
- filename = os.path.join(filename, self.variableNameFromId() + '.' + _format.lower())
- if DEBUG:
- print >> sys.stderr, "writeImageToFile: saving image to '%s' in %s format" % (filename, _format)
- #self.device.takeSnapshot().getSubImage(self.getPositionAndSize()).writeToFile(filename, _format)
- # crop:
- # im.crop(box) ⇒ image
- # Returns a copy of a rectangular region from the current image.
- # The box is a 4-tuple defining the left, upper, right, and lower pixel coordinate.
- ((l, t), (r, b)) = self.getCoords()
- box = (l, t, r, b)
- if DEBUG:
- print >> sys.stderr, "writeImageToFile: cropping", box, " reconnect=", self.device.reconnect
- self.device.takeSnapshot(reconnect=self.device.reconnect).crop(box).save(filename, _format)
- def __smallStr__(self):
- __str = unicode("View[", 'utf-8', 'replace')
- if "class" in self.map:
- __str += " class=" + self.map['class']
- __str += " id=%s" % self.getId()
- __str += " ] parent="
- if self.parent and "class" in self.parent.map:
- __str += "%s" % self.parent.map["class"]
- else:
- __str += "None"
- return __str
- def __tinyStr__(self):
- __str = unicode("View[", 'utf-8', 'replace')
- if "class" in self.map:
- __str += " class=" + re.sub('.*\.', '', self.map['class'])
- __str += " id=%s" % self.getId()
- __str += " ]"
- return __str
- def __microStr__(self):
- __str = unicode('', 'utf-8', 'replace')
- if "class" in self.map:
- __str += re.sub('.*\.', '', self.map['class'])
- _id = self.getId().replace('id/no_id/', '-')
- __str += _id
- ((L, T), (R, B)) = self.getCoords()
- __str += '@%04d%04d%04d%04d' % (L, T, R, B)
- __str += ''
- return __str
- def __str__(self):
- __str = unicode("View[", 'utf-8', 'replace')
- if "class" in self.map:
- __str += " class=" + self.map["class"].__str__() + " "
- for a in self.map:
- __str += a + "="
- # decode() works only on python's 8-bit strings
- if isinstance(self.map[a], unicode):
- __str += self.map[a]
- else:
- __str += unicode(str(self.map[a]), 'utf-8', errors='replace')
- __str += " "
- __str += "] parent="
- if self.parent:
- if "class" in self.parent.map:
- __str += "%s" % self.parent.map["class"]
- else:
- __str += self.parent.getId().__str__()
- else:
- __str += "None"
- return __str
- class TextView(View):
- '''
- TextView class.
- '''
- pass
- class EditText(TextView):
- '''
- EditText class.
- '''
- def type(self, text, alreadyTouched=False):
- if not alreadyTouched:
- self.touch()
- time.sleep(0.5)
- escaped = text.replace('%s', '\\%s')
- encoded = escaped.replace(' ', '%s')
- self.device.type(encoded)
- time.sleep(0.5)
- def setText(self, text):
- """
- This function makes sure that any previously entered text is deleted before
- setting the value of the field.
- """
- if self.text() == text:
- return
- self.touch()
- guardrail = 0
- maxSize = len(self.text()) + 1
- while maxSize > guardrail:
- guardrail += 1
- self.device.press('KEYCODE_DEL', adbclient.DOWN_AND_UP)
- self.device.press('KEYCODE_FORWARD_DEL', adbclient.DOWN_AND_UP)
- self.type(text, alreadyTouched=True)
- def backspace(self):
- self.touch()
- time.sleep(1)
- self.device.press('KEYCODE_DEL', adbclient.DOWN_AND_UP)
- class UiDevice():
- '''
- Provides access to state information about the device. You can also use this class to simulate
- user actions on the device, such as pressing the d-pad or pressing the Home and Menu buttons.
- '''
-
- def __init__(self, vc):
- self.vc = vc
- self.device = self.vc.device
-
- def openNotification(self):
- '''
- Opens the notification shade.
- '''
-
- # the tablet has a different Notification/Quick Settings bar depending on x
- w13 = self.device.display['width'] / 3
- s = (w13, 0)
- e = (w13, self.device.display['height']/2)
- self.device.drag(s, e, 500, 20, -1)
- self.vc.sleep(1)
- self.vc.dump(-1)
-
- def openQuickSettings(self):
- '''
- Opens the Quick Settings shade.
- '''
-
- # the tablet has a different Notification/Quick Settings bar depending on x
- w23 = 2 * self.device.display['width'] / 3
- s = (w23, 0)
- e = (w23, self.device.display['height']/2)
- self.device.drag(s, e, 500, 20, -1)
- self.vc.sleep(1)
- if self.vc.getSdkVersion() >= 20:
- self.device.drag(s, e, 500, 20, -1)
- self.vc.sleep(1)
- self.vc.dump(-1)
- def openQuickSettingsSettings(self):
- '''
- Opens the Quick Settings shade and then tries to open Settings from there.
- '''
- STATUS_BAR_SETTINGS_SETTINGS_BUTTON = [
- u"Settings", u"Cài đặt", u"Instellingen", u"Կարգավորումներ", u"设置", u"Nastavitve", u"සැකසීම්", u"Ayarlar",
- u"Setelan", u"Настройки", u"تنظیمات", u"Mga Setting", u"Тохиргоо", u"Configuració", u"Setări", u"Налады",
- u"Einstellungen", u"პარამეტრები", u"सेटिङहरू", u"Կարգավորումներ", u"Nustatymai", u"Beállítások", u"設定",
- u"सेटिंग", u"Настройки", u"Inställningar", u"設定", u"ການຕັ້ງຄ່າ", u"Configurações", u"Tetapan", u"설정",
- u"ការកំណត់", u"Ajustes", u"הגדרות", u"Ustawienia", u"Nastavení", u"Ρυθμίσεις", u"Тохиргоо", u"Ayarlar",
- u"Indstillinger", u"Налаштування", u"Mipangilio", u"Izilungiselelo", u"設定", u"Nastavenia", u"Paramètres",
- u"ቅንብሮች", u"การตั้งค่า", u"Seaded", u"Iestatījumi", u"Innstillinger", u"Подешавања", u"الإعدادات", u"සැකසීම්",
- u"Definições", u"Configuración", u"პარამეტრები", u"Postavke", u"Ayarlar", u"Impostazioni", u"Asetukset",
- u"Instellings", u"Seaded", u"ការកំណត់", u"सेटिङहरू", u"Tetapan"
- ]
-
- self.openQuickSettings()
- # this works on API >= 20
- found = False
- for s in STATUS_BAR_SETTINGS_SETTINGS_BUTTON:
- if DEBUG:
- print >> sys.stderr, u"finding view with cd=", type(s)
- view = self.vc.findViewWithContentDescription(u'''{0}'''.format(s))
- if view:
- found = True
- view.touch()
- break
-
- if not found:
- # for previous APIs, let's find the text
- for s in STATUS_BAR_SETTINGS_SETTINGS_BUTTON:
- if DEBUG:
- print >> sys.stderr, "s=", type(s)
- try:
- print >> sys.stderr, "finding view with text=", u'''{0}'''.format(s)
- except:
- pass
- view = self.vc.findViewWithText(s)
- if view:
- found = True
- view.touch()
- break
- if not found:
- raise ViewNotFoundException("content-description", "'Settings' or text 'Settings'", "ROOT")
-
- self.vc.sleep(1)
- self.vc.dump(window=-1)
-
- def changeLanguage(self, languageTo):
- LANGUAGE_SETTINGS = {
- "en": u"Language & input",
- "af": u"Taal en invoer",
- "am": u"ቋንቋ እና ግቤት",
- "ar": u"اللغة والإدخال",
- "az": u"Dil və daxiletmə",
- "az-rAZ": u"Dil və daxiletmə",
- "be": u"Мова і ўвод",
- "bg": u"Език и въвеждане",
- "ca": u"Idioma i introducció de text",
- "cs": u"Jazyk a zadávání",
- "da": u"Sprog og input",
- "de": u"Sprache & Eingabe",
- "el": u"Γλώσσα και εισαγωγή",
- "en-rGB": u"Language & input",
- "en-rIN": u"Language & input",
- "es": u"Idioma e introducción de texto",
- "es-rUS": u"Teclado e idioma",
- "et": u"Keeled ja sisestamine",
- "et-rEE": u"Keeled ja sisestamine",
- "fa": u"زبان و ورود اطلاعات",
- "fi": u"Kieli ja syöttötapa",
- "fr": u"Langue et saisie",
- "fr-rCA": u"Langue et saisie",
- "hi": u"भाषा और अक्षर",
- "hr": u"Jezik i ulaz",
- "hu": u"Nyelv és bevitel",
- "hy": u"Լեզվի & ներմուծում",
- "hy-rAM": u"Լեզու և ներմուծում",
- "in": u"Bahasa & masukan",
- "it": u"Lingua e immissione",
- "iw": u"שפה וקלט",
- "ja": u"言語と入力",
- "ka": u"ენისა და შეყვანის პარამეტრები",
- "ka-rGE": u"ენისა და შეყვანის პარამეტრები",
- "km": u"ភាសា & ការបញ្ចូល",
- "km-rKH": u"ភាសា & ការបញ្ចូល",
- "ko": u"언어 및 키보드",
- "lo": u"ພາສາ & ການປ້ອນຂໍ້ມູນ",
- "lo-rLA": u"ພາສາ & ການປ້ອນຂໍ້ມູນ",
- "lt": u"Kalba ir įvestis",
- "lv": u"Valodas ievade",
- "mn": u"Хэл & оруулах",
- "mn-rMN": u"Хэл & оруулах",
- "ms": u"Bahasa & input",
- "ms-rMY": u"Bahasa & input",
- "nb": u"Språk og inndata",
- "ne": u"भाषा र इनपुट",
- "ne-rNP": u"भाषा र इनपुट",
- "nl": u"Taal en invoer",
- "pl": u"Język, klawiatura, głos",
- "pt": u"Idioma e entrada",
- "pt-rPT": u"Idioma e entrada",
- "ro": u"Limbă și introducere de text",
- "ru": u"Язык и ввод",
- "si": u"භාෂාව සහ ආදානය",
- "si-rLK": u"භාෂාව සහ ආදානය",
- "sk": u"Jazyk & vstup",
- "sl": u"Jezik in vnos",
- "sr": u"Језик и унос",
- "sv": u"Språk och inmatning",
- "sw": u"Lugha, Kibodi na Sauti",
- "th": u"ภาษาและการป้อนข้อมูล",
- "tl": u"Wika at input",
- "tr": u"Dil ve giriş",
- "uk": u"Мова та введення",
- "vi": u"Ngôn ngữ & phương thức nhập",
- "zh-rCN": u"语言和输入法",
- "zh-rHK": u"語言與輸入裝置",
- "zh-rTW": u"語言與輸入設定",
- "zu": u"Ulimi & ukufakwa",
- }
- PHONE_LANGUAGE = {
- "en": u"Language",
- "af": u"Taal",
- "am": u"ቋንቋ",
- "ar": u"اللغة",
- "az": u"Dil",
- "az-rAZ": u"Dil",
- "be": u"Мова",
- "bg": u"Език",
- "ca": u"Idioma",
- "cs": u"Jazyk",
- "da": u"Sprog",
- "de": u"Sprache",
- "el": u"Γλώσσα",
- "en-rGB": u"Language",
- "en-rIN": u"Language",
- "es": u"Idioma",
- "es-rUS": u"Idioma",
- "et": u"Keel",
- "et-rEE": u"Keel",
- "fa": u"زبان",
- "fi": u"Kieli",
- "fr": u"Langue",
- "fr-rCA": u"Langue",
- "hi": u"भाषा",
- "hr": u"Jezik",
- "hu": u"Nyelv",
- "hy": u"Lեզուն",
- "hy-rAM": u"Lեզուն",
- "in": u"Bahasa",
- "it": u"Lingua",
- "iw": u"שפה",
- "ja": u"言語",
- "ka": u"ენა",
- "ka-rGE": u"ენა",
- "km": u"ភាសា",
- "km-rKH": u"ភាសា",
- "ko": u"언어",
- "lo": u"ພາສາ",
- "lo-rLA": u"ພາສາ",
- "lt": u"Kalba",
- "lv": u"Valoda",
- "mn": u"Хэл",
- "mn-rMN": u"Хэл",
- "ms": u"Bahasa",
- "ms-rMY": u"Bahasa",
- "nb": u"Språk",
- "ne": u"भाषा",
- "nl": u"Taal",
- "pl": u"Język",
- "pt": u"Idioma",
- "pt-rPT": u"Idioma",
- "ro": u"Limba",
- "ru": u"Язык",
- "si": u"භාෂාව",
- "si-rLK": u"භාෂාව",
- "sk": u"Jazyk",
- "sl": u"Jezik",
- "sr": u"Језик",
- "sv": u"Språk",
- "sw": u"Lugha",
- "th": u"ภาษา",
- "tl": u"Wika",
- "tr": u"Dil",
- "uk": u"Мова",
- "vi": u"Ngôn ngữ",
- "zh-rCN": u"语言",
- "zh-rHK": u"語言",
- "zh-rTW": u"語言",
- "zu": u"Ulimi",
- }
- LANGUAGES = {
- "en": u"English (United States)",
- "es-rUS": u"Español (Estados Unidos)",
- "af": u"Afrikaans", # Afrikaans
- "af-rNA": u"Afrikaans (Namibië)", # Afrikaans (Namibia)
- "af-rZA": u"Afrikaans (Suid-Afrika)", # Afrikaans (South Africa)
- "agq": u"Aghem", # Aghem
- "agq-rCM": u"Aghem (Kàmàlûŋ)", # Aghem (Cameroon)
- "ak": u"Akan", # Akan
- "ak-rGH": u"Akan (Gaana)", # Akan (Ghana)
- "am": u"አማርኛ", # Amharic
- "am-rET": u"አማርኛ (ኢትዮጵያ)", # Amharic (Ethiopia)
- "ar": u"العربية", # Arabic
- "ar_001": u"العربية (العالم)", # Arabic (World)
- "ar-rAE": u"العربية (الإمارات العربية المتحدة)", # Arabic (United Arab Emirates)
- "ar-rBH": u"العربية (البحرين)", # Arabic (Bahrain)
- "ar-rDJ": u"العربية (جيبوتي)", # Arabic (Djibouti)
- "ar-rDZ": u"العربية (الجزائر)", # Arabic (Algeria)
- "ar-rEG": u"العربية (مصر)", # Arabic (Egypt)
- "ar-rEH": u"العربية (الصحراء الغربية)", # Arabic (Western Sahara)
- "ar-rER": u"العربية (أريتريا)", # Arabic (Eritrea)
- "ar-rIL": u"العربية (إسرائيل)", # Arabic (Israel)
- "ar-rIQ": u"العربية (العراق)", # Arabic (Iraq)
- "ar-rJO": u"العربية (الأردن)", # Arabic (Jordan)
- "ar-rKM": u"العربية (جزر القمر)", # Arabic (Comoros)
- "ar-rKW": u"العربية (الكويت)", # Arabic (Kuwait)
- "ar-rLB": u"العربية (لبنان)", # Arabic (Lebanon)
- "ar-rLY": u"العربية (ليبيا)", # Arabic (Libya)
- "ar-rMA": u"العربية (المغرب)", # Arabic (Morocco)
- "ar-rMR": u"العربية (موريتانيا)", # Arabic (Mauritania)
- "ar-rOM": u"العربية (عُمان)", # Arabic (Oman)
- "ar-rPS": u"العربية (فلسطين)", # Arabic (Palestine)
- "ar-rQA": u"العربية (قطر)", # Arabic (Qatar)
- "ar-rSA": u"العربية (المملكة العربية السعودية)", # Arabic (Saudi Arabia)
- "ar-rSD": u"العربية (السودان)", # Arabic (Sudan)
- "ar-rSO": u"العربية (الصومال)", # Arabic (Somalia)
- "ar-rSY": u"العربية (سوريا)", # Arabic (Syria)
- "ar-rTD": u"العربية (تشاد)", # Arabic (Chad)
- "ar-rTN": u"العربية (تونس)", # Arabic (Tunisia)
- "ar-rYE": u"العربية (اليمن)", # Arabic (Yemen)
- "as": u"অসমীয়া", # Assamese
- "as-rIN": u"অসমীয়া (ভাৰত)", # Assamese (India)
- "asa": u"Kipare", # Asu
- "asa-rTZ": u"Kipare (Tadhania)", # Asu (Tanzania)
- "az": u"Azərbaycanca", # Azerbaijani
- "az-rCYRL": u"Азәрбајҹан (CYRL)", # Azerbaijani (CYRL)
- "az-rCYRL_AZ": u"Азәрбајҹан (Азәрбајҹан,AZ)", # Azerbaijani (Azerbaijan,AZ)
- "az-rLATN": u"Azərbaycanca (LATN)", # Azerbaijani (LATN)
- "az-rLATN_AZ": u"Azərbaycanca (Azərbaycan,AZ)", # Azerbaijani (Azerbaijan,AZ)
- "bas": u"Ɓàsàa", # Basaa
- "bas-rCM": u"Ɓàsàa (Kàmɛ̀rûn)", # Basaa (Cameroon)
- "be": u"беларуская", # Belarusian
- "be-rBY": u"беларуская (Беларусь)", # Belarusian (Belarus)
- "bem": u"Ichibemba", # Bemba
- "bem-rZM": u"Ichibemba (Zambia)", # Bemba (Zambia)
- "bez": u"Hibena", # Bena
- "bez-rTZ": u"Hibena (Hutanzania)", # Bena (Tanzania)
- "bg": u"български", # Bulgarian
- "bg-rBG": u"български (България)", # Bulgarian (Bulgaria)
- "bm": u"Bamanakan", # Bambara
- "bm-rML": u"Bamanakan (Mali)", # Bambara (Mali)
- "bn": u"বাংলা", # Bengali
- "bn-rBD": u"বাংলা (বাংলাদেশ)", # Bengali (Bangladesh)
- "bn-rIN": u"বাংলা (ভারত)", # Bengali (India)
- "bo": u"པོད་སྐད་", # Tibetan
- "bo-rCN": u"པོད་སྐད་ (རྒྱ་ནག)", # Tibetan (China)
- "bo-rIN": u"པོད་སྐད་ (རྒྱ་གར་)", # Tibetan (India)
- "br": u"Brezhoneg", # Breton
- "br-rFR": u"Brezhoneg (Frañs)", # Breton (France)
- "brx": u"बड़ो", # Bodo
- "brx-rIN": u"बड़ो (भारत)", # Bodo (India)
- "bs": u"Bosanski", # Bosnian
- "bs-rCYRL": u"босански (CYRL)", # Bosnian (CYRL)
- "bs-rCYRL_BA": u"босански (Босна и Херцеговина,BA)", # Bosnian (Bosnia and Herzegovina,BA)
- "bs-rLATN": u"Bosanski (LATN)", # Bosnian (LATN)
- "bs-rLATN_BA": u"Bosanski (Bosna i Hercegovina,BA)", # Bosnian (Bosnia and Herzegovina,BA)
- "ca": u"Català", # Catalan
- "ca-rAD": u"Català (Andorra)", # Catalan (Andorra)
- "ca-rES": u"Català (Espanya)", # Catalan (Spain)
- "cgg": u"Rukiga", # Chiga
- "cgg-rUG": u"Rukiga (Uganda)", # Chiga (Uganda)
- "chr": u"ᏣᎳᎩ", # Cherokee
- "chr-rUS": u"ᏣᎳᎩ (ᎠᎹᏰᏟ)", # Cherokee (United States)
- "cs": u"čeština", # Czech
- "cs-rCZ": u"čeština (Česká republika)", # Czech (Czech Republic)
- "cy": u"Cymraeg", # Welsh
- "cy-rGB": u"Cymraeg (y Deyrnas Unedig)", # Welsh (United Kingdom)
- "da": u"Dansk", # Danish
- "da-rDK": u"Dansk (Danmark)", # Danish (Denmark)
- "dav": u"Kitaita", # Taita
- "dav-rKE": u"Kitaita (Kenya)", # Taita (Kenya)
- "de": u"Deutsch", # German
- "de-rAT": u"Deutsch (Österreich)", # German (Austria)
- "de-rBE": u"Deutsch (Belgien)", # German (Belgium)
- "de-rCH": u"Deutsch (Schweiz)", # German (Switzerland)
- "de-rDE": u"Deutsch (Deutschland)", # German (Germany)
- "de-rLI": u"Deutsch (Liechtenstein)", # German (Liechtenstein)
- "de-rLU": u"Deutsch (Luxemburg)", # German (Luxembourg)
- "dje": u"Zarmaciine", # Zarma
- "dje-rNE": u"Zarmaciine (Nižer)", # Zarma (Niger)
- "dua": u"Duálá", # Duala
- "dua-rCM": u"Duálá (Cameroun)", # Duala (Cameroon)
- "dyo": u"Joola", # Jola-Fonyi
- "dyo-rSN": u"Joola (Senegal)", # Jola-Fonyi (Senegal)
- "dz": u"རྫོང་ཁ", # Dzongkha
- "dz-rBT": u"རྫོང་ཁ (འབྲུག)", # Dzongkha (Bhutan)
- "ebu": u"Kĩembu", # Embu
- "ebu-rKE": u"Kĩembu (Kenya)", # Embu (Kenya)
- "ee": u"Eʋegbe", # Ewe
- "ee-rGH": u"Eʋegbe (Ghana nutome)", # Ewe (Ghana)
- "ee-rTG": u"Eʋegbe (Togo nutome)", # Ewe (Togo)
- "el": u"Ελληνικά", # Greek
- "el-rCY": u"Ελληνικά (Κύπρος)", # Greek (Cyprus)
- "el-rGR": u"Ελληνικά (Ελλάδα)", # Greek (Greece)
- "en": u"English", # English
- "en_150": u"English (Europe)", # English (Europe)
- "en-rAG": u"English (Antigua and Barbuda)", # English (Antigua and Barbuda)
- "en-rAS": u"English (American Samoa)", # English (American Samoa)
- "en-rAU": u"English (Australia)", # English (Australia)
- "en-rBB": u"English (Barbados)", # English (Barbados)
- "en-rBE": u"English (Belgium)", # English (Belgium)
- "en-rBM": u"English (Bermuda)", # English (Bermuda)
- "en-rBS": u"English (Bahamas)", # English (Bahamas)
- "en-rBW": u"English (Botswana)", # English (Botswana)
- "en-rBZ": u"English (Belize)", # English (Belize)
- "en-rCA": u"English (Canada)", # English (Canada)
- "en-rCM": u"English (Cameroon)", # English (Cameroon)
- "en-rDM": u"English (Dominica)", # English (Dominica)
- "en-rFJ": u"English (Fiji)", # English (Fiji)
- "en-rFM": u"English (Micronesia)", # English (Micronesia)
- "en-rGB": u"English (United Kingdom)", # English (United Kingdom)
- "en-rGD": u"English (Grenada)", # English (Grenada)
- "en-rGG": u"English (Guernsey)", # English (Guernsey)
- "en-rGH": u"English (Ghana)", # English (Ghana)
- "en-rGI": u"English (Gibraltar)", # English (Gibraltar)
- "en-rGM": u"English (Gambia)", # English (Gambia)
- "en-rGU": u"English (Guam)", # English (Guam)
- "en-rGY": u"English (Guyana)", # English (Guyana)
- "en-rHK": u"English (Hong Kong)", # English (Hong Kong)
- "en-rIE": u"English (Ireland)", # English (Ireland)
- "en-rIM": u"English (Isle of Man)", # English (Isle of Man)
- "en-rIN": u"English (India)", # English (India)
- "en-rJE": u"English (Jersey)", # English (Jersey)
- "en-rJM": u"English (Jamaica)", # English (Jamaica)
- "en-rKE": u"English (Kenya)", # English (Kenya)
- "en-rKI": u"English (Kiribati)", # English (Kiribati)
- "en-rKN": u"English (Saint Kitts and Nevis)", # English (Saint Kitts and Nevis)
- "en-rKY": u"English (Cayman Islands)", # English (Cayman Islands)
- "en-rLC": u"English (Saint Lucia)", # English (Saint Lucia)
- "en-rLR": u"English (Liberia)", # English (Liberia)
- "en-rLS": u"English (Lesotho)", # English (Lesotho)
- "en-rMG": u"English (Madagascar)", # English (Madagascar)
- "en-rMH": u"English (Marshall Islands)", # English (Marshall Islands)
- "en-rMP": u"English (Northern Mariana Islands)", # English (Northern Mariana Islands)
- "en-rMT": u"English (Malta)", # English (Malta)
- "en-rMU": u"English (Mauritius)", # English (Mauritius)
- "en-rMW": u"English (Malawi)", # English (Malawi)
- "en-rNA": u"English (Namibia)", # English (Namibia)
- "en-rNG": u"English (Nigeria)", # English (Nigeria)
- "en-rNZ": u"English (New Zealand)", # English (New Zealand)
- "en-rPG": u"English (Papua New Guinea)", # English (Papua New Guinea)
- "en-rPH": u"English (Philippines)", # English (Philippines)
- "en-rPK": u"English (Pakistan)", # English (Pakistan)
- "en-rPR": u"English (Puerto Rico)", # English (Puerto Rico)
- "en-rPW": u"English (Palau)", # English (Palau)
- "en-rSB": u"English (Solomon Islands)", # English (Solomon Islands)
- "en-rSC": u"English (Seychelles)", # English (Seychelles)
- "en-rSG": u"English (Singapore)", # English (Singapore)
- "en-rSL": u"English (Sierra Leone)", # English (Sierra Leone)
- "en-rSS": u"English (South Sudan)", # English (South Sudan)
- "en-rSZ": u"English (Swaziland)", # English (Swaziland)
- "en-rTC": u"English (Turks and Caicos Islands)", # English (Turks and Caicos Islands)
- "en-rTO": u"English (Tonga)", # English (Tonga)
- "en-rTT": u"English (Trinidad and Tobago)", # English (Trinidad and Tobago)
- "en-rTZ": u"English (Tanzania)", # English (Tanzania)
- "en-rUG": u"English (Uganda)", # English (Uganda)
- "en-rUM": u"English (U.S. Outlying Islands)", # English (U.S. Outlying Islands)
- "en-rUS": u"English (United States)", # English (United States)
- "en-rUS_POSIX": u"English (United States,Computer)", # English (United States,Computer)
- "en-rVC": u"English (Saint Vincent and the Grenadines)", # English (Saint Vincent and the Grenadines)
- "en-rVG": u"English (British Virgin Islands)", # English (British Virgin Islands)
- "en-rVI": u"English (U.S. Virgin Islands)", # English (U.S. Virgin Islands)
- "en-rVU": u"English (Vanuatu)", # English (Vanuatu)
- "en-rWS": u"English (Samoa)", # English (Samoa)
- "en-rZA": u"English (South Africa)", # English (South Africa)
- "en-rZM": u"English (Zambia)", # English (Zambia)
- "en-rZW": u"English (Zimbabwe)", # English (Zimbabwe)
- "eo": u"Esperanto", # Esperanto
- "es": u"Español", # Spanish
- "es_419": u"Español (Latinoamérica)", # Spanish (Latin America)
- "es-rAR": u"Español (Argentina)", # Spanish (Argentina)
- "es-rBO": u"Español (Bolivia)", # Spanish (Bolivia)
- "es-rCL": u"Español (Chile)", # Spanish (Chile)
- "es-rCO": u"Español (Colombia)", # Spanish (Colombia)
- "es-rCR": u"Español (Costa Rica)", # Spanish (Costa Rica)
- "es-rCU": u"Español (Cuba)", # Spanish (Cuba)
- "es-rDO": u"Español (República Dominicana)", # Spanish (Dominican Republic)
- "es-rEA": u"Español (Ceuta y Melilla)", # Spanish (Ceuta and Melilla)
- "es-rEC": u"Español (Ecuador)", # Spanish (Ecuador)
- "es-rES": u"Español (España)", # Spanish (Spain)
- "es-rGQ": u"Español (Guinea Ecuatorial)", # Spanish (Equatorial Guinea)
- "es-rGT": u"Español (Guatemala)", # Spanish (Guatemala)
- "es-rHN": u"Español (Honduras)", # Spanish (Honduras)
- "es-rIC": u"Español (Islas Canarias)", # Spanish (Canary Islands)
- "es-rMX": u"Español (México)", # Spanish (Mexico)
- "es-rNI": u"Español (Nicaragua)", # Spanish (Nicaragua)
- "es-rPA": u"Español (Panamá)", # Spanish (Panama)
- "es-rPE": u"Español (Perú)", # Spanish (Peru)
- "es-rPH": u"Español (Filipinas)", # Spanish (Philippines)
- "es-rPR": u"Español (Puerto Rico)", # Spanish (Puerto Rico)
- "es-rPY": u"Español (Paraguay)", # Spanish (Paraguay)
- "es-rSV": u"Español (El Salvador)", # Spanish (El Salvador)
- "es-rUS": u"Español (Estados Unidos)", # Spanish (United States)
- "es-rUY": u"Español (Uruguay)", # Spanish (Uruguay)
- "es-rVE": u"Español (Venezuela)", # Spanish (Venezuela)
- "et": u"Eesti", # Estonian
- "et-rEE": u"Eesti (Eesti)", # Estonian (Estonia)
- "eu": u"Euskara", # Basque
- "eu-rES": u"Euskara (Espainia)", # Basque (Spain)
- "ewo": u"Ewondo", # Ewondo
- "ewo-rCM": u"Ewondo (Kamərún)", # Ewondo (Cameroon)
- "fa": u"فارسی", # Persian
- "fa-rAF": u"دری (افغانستان)", # Persian (Afghanistan)
- "fa-rIR": u"فارسی (ایران)", # Persian (Iran)
- "ff": u"Pulaar", # Fulah
- "ff-rSN": u"Pulaar (Senegaal)", # Fulah (Senegal)
- "fi": u"Suomi", # Finnish
- "fi-rFI": u"Suomi (Suomi)", # Finnish (Finland)
- "fil": u"Filipino", # Filipino
- "fil-rPH": u"Filipino (Pilipinas)", # Filipino (Philippines)
- "fo": u"Føroyskt", # Faroese
- "fo-rFO": u"Føroyskt (Føroyar)", # Faroese (Faroe Islands)
- "fr": u"Français", # French
- "fr-rBE": u"Français (Belgique)", # French (Belgium)
- "fr-rBF": u"Français (Burkina Faso)", # French (Burkina Faso)
- "fr-rBI": u"Français (Burundi)", # French (Burundi)
- "fr-rBJ": u"Français (Bénin)", # French (Benin)
- "fr-rBL": u"Français (Saint-Barthélémy)", # French (Saint Barthélemy)
- "fr-rCA": u"Français (Canada)", # French (Canada)
- "fr-rCD": u"Français (République démocratique du Congo)", # French (Congo [DRC])
- "fr-rCF": u"Français (République centrafricaine)", # French (Central African Republic)
- "fr-rCG": u"Français (Congo-Brazzaville)", # French (Congo [Republic])
- "fr-rCH": u"Français (Suisse)", # French (Switzerland)
- "fr-rCI": u"Français (Côte d’Ivoire)", # French (Côte d’Ivoire)
- "fr-rCM": u"Français (Cameroun)", # French (Cameroon)
- "fr-rDJ": u"Français (Djibouti)", # French (Djibouti)
- "fr-rDZ": u"Français (Algérie)", # French (Algeria)
- "fr-rFR": u"Français (France)", # French (France)
- "fr-rGA": u"Français (Gabon)", # French (Gabon)
- "fr-rGF": u"Français (Guyane française)", # French (French Guiana)
- "fr-rGN": u"Français (Guinée)", # French (Guinea)
- "fr-rGP": u"Français (Guadeloupe)", # French (Guadeloupe)
- "fr-rGQ": u"Français (Guinée équatoriale)", # French (Equatorial Guinea)
- "fr-rHT": u"Français (Haïti)", # French (Haiti)
- "fr-rKM": u"Français (Comores)", # French (Comoros)
- "fr-rLU": u"Français (Luxembourg)", # French (Luxembourg)
- "fr-rMA": u"Français (Maroc)", # French (Morocco)
- "fr-rMC": u"Français (Monaco)", # French (Monaco)
- "fr-rMF": u"Français (Saint-Martin [partie française])", # French (Saint Martin)
- "fr-rMG": u"Français (Madagascar)", # French (Madagascar)
- "fr-rML": u"Français (Mali)", # French (Mali)
- "fr-rMQ": u"Français (Martinique)", # French (Martinique)
- "fr-rMR": u"Français (Mauritanie)", # French (Mauritania)
- "fr-rMU": u"Français (Maurice)", # French (Mauritius)
- "fr-rNC": u"Français (Nouvelle-Calédonie)", # French (New Caledonia)
- "fr-rNE": u"Français (Niger)", # French (Niger)
- "fr-rPF": u"Français (Polynésie française)", # French (French Polynesia)
- "fr-rRE": u"Français (Réunion)", # French (Réunion)
- "fr-rRW": u"Français (Rwanda)", # French (Rwanda)
- "fr-rSC": u"Français (Seychelles)", # French (Seychelles)
- "fr-rSN": u"Français (Sénégal)", # French (Senegal)
- "fr-rSY": u"Français (Syrie)", # French (Syria)
- "fr-rTD": u"Français (Tchad)", # French (Chad)
- "fr-rTG": u"Français (Togo)", # French (Togo)
- "fr-rTN": u"Français (Tunisie)", # French (Tunisia)
- "fr-rVU": u"Français (Vanuatu)", # French (Vanuatu)
- "fr-rYT": u"Français (Mayotte)", # French (Mayotte)
- "ga": u"Gaeilge", # Irish
- "ga-rIE": u"Gaeilge (Éire)", # Irish (Ireland)
- "gl": u"Galego", # Galician
- "gl-rES": u"Galego (España)", # Galician (Spain)
- "gsw": u"Schwiizertüütsch", # Swiss German
- "gsw-rCH": u"Schwiizertüütsch (Schwiiz)", # Swiss German (Switzerland)
- "gu": u"ગુજરાતી", # Gujarati
- "gu-rIN": u"ગુજરાતી (ભારત)", # Gujarati (India)
- "guz": u"Ekegusii", # Gusii
- "guz-rKE": u"Ekegusii (Kenya)", # Gusii (Kenya)
- "gv": u"Gaelg", # Manx
- "gv-rGB": u"Gaelg (Rywvaneth Unys)", # Manx (United Kingdom)
- "ha": u"Hausa", # Hausa
- "ha-rLATN": u"Hausa (LATN)", # Hausa (LATN)
- "ha-rLATN_GH": u"Hausa (Gana,GH)", # Hausa (Ghana,GH)
- "ha-rLATN_NE": u"Hausa (Nijar,NE)", # Hausa (Niger,NE)
- "ha-rLATN_NG": u"Hausa (Najeriya,NG)", # Hausa (Nigeria,NG)
- "haw": u"ʻŌlelo Hawaiʻi", # Hawaiian
- "haw-rUS": u"ʻŌlelo Hawaiʻi (ʻAmelika Hui Pū ʻIa)", # Hawaiian (United States)
- "iw": u"עברית", # Hebrew
- "iw-rIL": u"עברית (ישראל)", # Hebrew (Israel)
- "hi": u"हिन्दी", # Hindi
- "hi-rIN": u"हिन्दी (भारत)", # Hindi (India)
- "hr": u"Hrvatski", # Croatian
- "hr-rBA": u"Hrvatski (Bosna i Hercegovina)", # Croatian (Bosnia and Herzegovina)
- "hr-rHR": u"Hrvatski (Hrvatska)", # Croatian (Croatia)
- "hu": u"Magyar", # Hungarian
- "hu-rHU": u"Magyar (Magyarország)", # Hungarian (Hungary)
- "hy": u"հայերեն", # Armenian
- "hy-rAM": u"հայերեն (Հայաստան)", # Armenian (Armenia)
- "in": u"Bahasa Indonesia", # Indonesian
- "in-rID": u"Bahasa Indonesia (Indonesia)", # Indonesian (Indonesia)
- "ig": u"Igbo", # Igbo
- "ig-rNG": u"Igbo (Nigeria)", # Igbo (Nigeria)
- "ii": u"ꆈꌠꉙ", # Sichuan Yi
- "ii-rCN": u"ꆈꌠꉙ (ꍏꇩ)", # Sichuan Yi (China)
- "is": u"íslenska", # Icelandic
- "is-rIS": u"íslenska (Ísland)", # Icelandic (Iceland)
- "it": u"Italiano", # Italian
- "it-rCH": u"Italiano (Svizzera)", # Italian (Switzerland)
- "it-rIT": u"Italiano (Italia)", # Italian (Italy)
- "it-rSM": u"Italiano (San Marino)", # Italian (San Marino)
- "ja": u"日本語", # Japanese
- "ja-rJP": u"日本語 (日本)", # Japanese (Japan)
- "jgo": u"Ndaꞌa", # Ngomba
- "jgo-rCM": u"Ndaꞌa (Kamɛlûn)", # Ngomba (Cameroon)
- "jmc": u"Kimachame", # Machame
- "jmc-rTZ": u"Kimachame (Tanzania)", # Machame (Tanzania)
- "ka": u"ქართული", # Georgian
- "ka-rGE": u"ქართული (საქართველო)", # Georgian (Georgia)
- "kab": u"Taqbaylit", # Kabyle
- "kab-rDZ": u"Taqbaylit (Lezzayer)", # Kabyle (Algeria)
- "kam": u"Kikamba", # Kamba
- "kam-rKE": u"Kikamba (Kenya)", # Kamba (Kenya)
- "kde": u"Chimakonde", # Makonde
- "kde-rTZ": u"Chimakonde (Tanzania)", # Makonde (Tanzania)
- "kea": u"Kabuverdianu", # Kabuverdianu
- "kea-rCV": u"Kabuverdianu (Kabu Verdi)", # Kabuverdianu (Cape Verde)
- "khq": u"Koyra ciini", # Koyra Chiini
- "khq-rML": u"Koyra ciini (Maali)", # Koyra Chiini (Mali)
- "ki": u"Gikuyu", # Kikuyu
- "ki-rKE": u"Gikuyu (Kenya)", # Kikuyu (Kenya)
- "kk": u"қазақ тілі", # Kazakh
- "kk-rCYRL": u"қазақ тілі (CYRL)", # Kazakh (CYRL)
- "kk-rCYRL_KZ": u"қазақ тілі (Қазақстан,KZ)", # Kazakh (Kazakhstan,KZ)
- "kl": u"Kalaallisut", # Kalaallisut
- "kl-rGL": u"Kalaallisut (Kalaallit Nunaat)", # Kalaallisut (Greenland)
- "kln": u"Kalenjin", # Kalenjin
- "kln-rKE": u"Kalenjin (Emetab Kenya)", # Kalenjin (Kenya)
- "km": u"ខ្មែរ", # Khmer
- "km-rKH": u"ខ្មែរ (កម្ពុជា)", # Khmer (Cambodia)
- "kn": u"ಕನ್ನಡ", # Kannada
- "kn-rIN": u"ಕನ್ನಡ (ಭಾರತ)", # Kannada (India)
- "ko": u"한국어", # Korean
- "ko-rKP": u"한국어 (조선 민주주의 인민 공화국)", # Korean (North Korea)
- "ko-rKR": u"한국어 (대한민국)", # Korean (South Korea)
- "kok": u"कोंकणी", # Konkani
- "kok-rIN": u"कोंकणी (भारत)", # Konkani (India)
- "ks": u"کٲشُر", # Kashmiri
- "ks-rARAB": u"کٲشُر (ARAB)", # Kashmiri (ARAB)
- "ks-rARAB_IN": u"کٲشُر (ہِنٛدوستان,IN)", # Kashmiri (India,IN)
- "ksb": u"Kishambaa", # Shambala
- "ksb-rTZ": u"Kishambaa (Tanzania)", # Shambala (Tanzania)
- "ksf": u"Rikpa", # Bafia
- "ksf-rCM": u"Rikpa (kamɛrún)", # Bafia (Cameroon)
- "kw": u"Kernewek", # Cornish
- "kw-rGB": u"Kernewek (Rywvaneth Unys)", # Cornish (United Kingdom)
- "lag": u"Kɨlaangi", # Langi
- "lag-rTZ": u"Kɨlaangi (Taansanía)", # Langi (Tanzania)
- "lg": u"Luganda", # Ganda
- "lg-rUG": u"Luganda (Yuganda)", # Ganda (Uganda)
- "ln": u"Lingála", # Lingala
- "ln-rAO": u"Lingála (Angóla)", # Lingala (Angola)
- "ln-rCD": u"Lingála (Repibiki demokratiki ya Kongó)", # Lingala (Congo [DRC])
- "ln-rCF": u"Lingála (Repibiki ya Afríka ya Káti)", # Lingala (Central African Republic)
- "ln-rCG": u"Lingála (Kongo)", # Lingala (Congo [Republic])
- "lo": u"ລາວ", # Lao
- "lo-rLA": u"ລາວ (ສ.ປ.ປ ລາວ)", # Lao (Laos)
- "lt": u"Lietuvių", # Lithuanian
- "lt-rLT": u"Lietuvių (Lietuva)", # Lithuanian (Lithuania)
- "lu": u"Tshiluba", # Luba-Katanga
- "lu-rCD": u"Tshiluba (Ditunga wa Kongu)", # Luba-Katanga (Congo [DRC])
- "luo": u"Dholuo", # Luo
- "luo-rKE": u"Dholuo (Kenya)", # Luo (Kenya)
- "luy": u"Luluhia", # Luyia
- "luy-rKE": u"Luluhia (Kenya)", # Luyia (Kenya)
- "lv": u"Latviešu", # Latvian
- "lv-rLV": u"Latviešu (Latvija)", # Latvian (Latvia)
- "mas": u"Maa", # Masai
- "mas-rKE": u"Maa (Kenya)", # Masai (Kenya)
- "mas-rTZ": u"Maa (Tansania)", # Masai (Tanzania)
- "mer": u"Kĩmĩrũ", # Meru
- "mer-rKE": u"Kĩmĩrũ (Kenya)", # Meru (Kenya)
- "mfe": u"Kreol morisien", # Morisyen
- "mfe-rMU": u"Kreol morisien (Moris)", # Morisyen (Mauritius)
- "mg": u"Malagasy", # Malagasy
- "mg-rMG": u"Malagasy (Madagasikara)", # Malagasy (Madagascar)
- "mgh": u"Makua", # Makhuwa-Meetto
- "mgh-rMZ": u"Makua (Umozambiki)", # Makhuwa-Meetto (Mozambique)
- "mgo": u"Metaʼ", # Meta'
- "mgo-rCM": u"Metaʼ (Kamalun)", # Meta' (Cameroon)
- "mk": u"македонски", # Macedonian
- "mk-rMK": u"македонски (Македонија)", # Macedonian (Macedonia [FYROM])
- "ml": u"മലയാളം", # Malayalam
- "ml-rIN": u"മലയാളം (ഇന്ത്യ)", # Malayalam (India)
- "mn": u"монгол", # Mongolian
- "mn-rCYRL": u"монгол (CYRL)", # Mongolian (CYRL)
- "mn-rCYRL_MN": u"монгол (Монгол,MN)", # Mongolian (Mongolia,MN)
- "mr": u"मराठी", # Marathi
- "mr-rIN": u"मराठी (भारत)", # Marathi (India)
- "ms": u"Bahasa Melayu", # Malay
- "ms-rLATN": u"Bahasa Melayu (LATN)", # Malay (LATN)
- "ms-rLATN_BN": u"Bahasa Melayu (Brunei,BN)", # Malay (Brunei,BN)
- "ms-rLATN_MY": u"Bahasa Melayu (Malaysia,MY)", # Malay (Malaysia,MY)
- "ms-rLATN_SG": u"Bahasa Melayu (Singapura,SG)", # Malay (Singapore,SG)
- "mt": u"Malti", # Maltese
- "mt-rMT": u"Malti (Malta)", # Maltese (Malta)
- "mua": u"MUNDAŊ", # Mundang
- "mua-rCM": u"MUNDAŊ (kameruŋ)", # Mundang (Cameroon)
- "my": u"ဗမာ", # Burmese
- "my-rMM": u"ဗမာ (မြန်မာ)", # Burmese (Myanmar [Burma])
- "naq": u"Khoekhoegowab", # Nama
- "naq-rNA": u"Khoekhoegowab (Namibiab)", # Nama (Namibia)
- "nb": u"Norsk bokmål", # Norwegian Bokmål
- "nb-rNO": u"Norsk bokmål (Norge)", # Norwegian Bokmål (Norway)
- "nd": u"IsiNdebele", # North Ndebele
- "nd-rZW": u"IsiNdebele (Zimbabwe)", # North Ndebele (Zimbabwe)
- "ne": u"नेपाली", # Nepali
- "ne-rIN": u"नेपाली (भारत)", # Nepali (India)
- "ne-rNP": u"नेपाली (नेपाल)", # Nepali (Nepal)
- "nl": u"Nederlands", # Dutch
- "nl-rAW": u"Nederlands (Aruba)", # Dutch (Aruba)
- "nl-rBE": u"Nederlands (België)", # Dutch (Belgium)
- "nl-rCW": u"Nederlands (Curaçao)", # Dutch (Curaçao)
- "nl-rNL": u"Nederlands (Nederland)", # Dutch (Netherlands)
- "nl-rSR": u"Nederlands (Suriname)", # Dutch (Suriname)
- "nl-rSX": u"Nederlands (Sint-Maarten)", # Dutch (Sint Maarten)
- "nmg": u"Nmg", # Kwasio
- "nmg-rCM": u"Nmg (Kamerun)", # Kwasio (Cameroon)
- "nn": u"Nynorsk", # Norwegian Nynorsk
- "nn-rNO": u"Nynorsk (Noreg)", # Norwegian Nynorsk (Norway)
- "nus": u"Thok Nath", # Nuer
- "nus-rSD": u"Thok Nath (Sudan)", # Nuer (Sudan)
- "nyn": u"Runyankore", # Nyankole
- "nyn-rUG": u"Runyankore (Uganda)", # Nyankole (Uganda)
- "om": u"Oromoo", # Oromo
- "om-rET": u"Oromoo (Itoophiyaa)", # Oromo (Ethiopia)
- "om-rKE": u"Oromoo (Keeniyaa)", # Oromo (Kenya)
- "or": u"ଓଡ଼ିଆ", # Oriya
- "or-rIN": u"ଓଡ଼ିଆ (ଭାରତ)", # Oriya (India)
- "pa": u"ਪੰਜਾਬੀ", # Punjabi
- "pa-rARAB": u"پنجاب (ARAB)", # Punjabi (ARAB)
- "pa-rARAB_PK": u"پنجاب (پکستان,PK)", # Punjabi (Pakistan,PK)
- "pa-rGURU": u"ਪੰਜਾਬੀ (GURU)", # Punjabi (GURU)
- "pa-rGURU_IN": u"ਪੰਜਾਬੀ (ਭਾਰਤ,IN)", # Punjabi (India,IN)
- "pl": u"Polski", # Polish
- "pl-rPL": u"Polski (Polska)", # Polish (Poland)
- "ps": u"پښتو", # Pashto
- "ps-rAF": u"پښتو (افغانستان)", # Pashto (Afghanistan)
- "pt": u"Português", # Portuguese
- "pt-rAO": u"Português (Angola)", # Portuguese (Angola)
- "pt-rBR": u"Português (Brasil)", # Portuguese (Brazil)
- "pt-rCV": u"Português (Cabo Verde)", # Portuguese (Cape Verde)
- "pt-rGW": u"Português (Guiné Bissau)", # Portuguese (Guinea-Bissau)
- "pt-rMO": u"Português (Macau)", # Portuguese (Macau)
- "pt-rMZ": u"Português (Moçambique)", # Portuguese (Mozambique)
- "pt-rPT": u"Português (Portugal)", # Portuguese (Portugal)
- "pt-rST": u"Português (São Tomé e Príncipe)", # Portuguese (São Tomé and Príncipe)
- "pt-rTL": u"Português (Timor-Leste)", # Portuguese (Timor-Leste)
- "rm": u"Rumantsch", # Romansh
- "rm-rCH": u"Rumantsch (Svizra)", # Romansh (Switzerland)
- "rn": u"Ikirundi", # Rundi
- "rn-rBI": u"Ikirundi (Uburundi)", # Rundi (Burundi)
- "ro": u"Română", # Romanian
- "ro-rMD": u"Română (Republica Moldova)", # Romanian (Moldova)
- "ro-rRO": u"Română (România)", # Romanian (Romania)
- "rof": u"Kihorombo", # Rombo
- "rof-rTZ": u"Kihorombo (Tanzania)", # Rombo (Tanzania)
- "ru": u"русский", # Russian
- "ru-rBY": u"русский (Беларусь)", # Russian (Belarus)
- "ru-rKG": u"русский (Киргизия)", # Russian (Kyrgyzstan)
- "ru-rKZ": u"русский (Казахстан)", # Russian (Kazakhstan)
- "ru-rMD": u"русский (Молдова)", # Russian (Moldova)
- "ru-rRU": u"русский (Россия)", # Russian (Russia)
- "ru-rUA": u"русский (Украина)", # Russian (Ukraine)
- "rw": u"Kinyarwanda", # Kinyarwanda
- "rw-rRW": u"Kinyarwanda (Rwanda)", # Kinyarwanda (Rwanda)
- "rwk": u"Kiruwa", # Rwa
- "rwk-rTZ": u"Kiruwa (Tanzania)", # Rwa (Tanzania)
- "saq": u"Kisampur", # Samburu
- "saq-rKE": u"Kisampur (Kenya)", # Samburu (Kenya)
- "sbp": u"Ishisangu", # Sangu
- "sbp-rTZ": u"Ishisangu (Tansaniya)", # Sangu (Tanzania)
- "seh": u"Sena", # Sena
- "seh-rMZ": u"Sena (Moçambique)", # Sena (Mozambique)
- "ses": u"Koyraboro senni", # Koyraboro Senni
- "ses-rML": u"Koyraboro senni (Maali)", # Koyraboro Senni (Mali)
- "sg": u"Sängö", # Sango
- "sg-rCF": u"Sängö (Ködörösêse tî Bêafrîka)", # Sango (Central African Republic)
- "shi": u"ⵜⴰⵎⴰⵣⵉⵖⵜ", # Tachelhit
- "shi-rLATN": u"Tamazight (LATN)", # Tachelhit (LATN)
- "shi-rLATN_MA": u"Tamazight (lmɣrib,MA)", # Tachelhit (Morocco,MA)
- "shi-rTFNG": u"ⵜⴰⵎⴰⵣⵉⵖⵜ (TFNG)", # Tachelhit (TFNG)
- "shi-rTFNG_MA": u"ⵜⴰⵎⴰⵣⵉⵖⵜ (ⵍⵎⵖⵔⵉⴱ,MA)", # Tachelhit (Morocco,MA)
- "si": u"සිංහල", # Sinhala
- "si-rLK": u"සිංහල (ශ්රී ලංකාව)", # Sinhala (Sri Lanka)
- "sk": u"Slovenčina", # Slovak
- "sk-rSK": u"Slovenčina (Slovensko)", # Slovak (Slovakia)
- "sl": u"Slovenščina", # Slovenian
- "sl-rSI": u"Slovenščina (Slovenija)", # Slovenian (Slovenia)
- "sn": u"ChiShona", # Shona
- "sn-rZW": u"ChiShona (Zimbabwe)", # Shona (Zimbabwe)
- "so": u"Soomaali", # Somali
- "so-rDJ": u"Soomaali (Jabuuti)", # Somali (Djibouti)
- "so-rET": u"Soomaali (Itoobiya)", # Somali (Ethiopia)
- "so-rKE": u"Soomaali (Kiiniya)", # Somali (Kenya)
- "so-rSO": u"Soomaali (Soomaaliya)", # Somali (Somalia)
- "sq": u"Shqip", # Albanian
- "sq-rAL": u"Shqip (Shqipëria)", # Albanian (Albania)
- "sq-rMK": u"Shqip (Maqedoni)", # Albanian (Macedonia [FYROM])
- "sr": u"Српски", # Serbian
- "sr-rCYRL": u"Српски (CYRL)", # Serbian (CYRL)
- "sr-rCYRL_BA": u"Српски (Босна и Херцеговина,BA)", # Serbian (Bosnia and Herzegovina,BA)
- "sr-rCYRL_ME": u"Српски (Црна Гора,ME)", # Serbian (Montenegro,ME)
- "sr-rCYRL_RS": u"Српски (Србија,RS)", # Serbian (Serbia,RS)
- "sr-rLATN": u"Srpski (LATN)", # Serbian (LATN)
- "sr-rLATN_BA": u"Srpski (Bosna i Hercegovina,BA)", # Serbian (Bosnia and Herzegovina,BA)
- "sr-rLATN_ME": u"Srpski (Crna Gora,ME)", # Serbian (Montenegro,ME)
- "sr-rLATN_RS": u"Srpski (Srbija,RS)", # Serbian (Serbia,RS)
- "sv": u"Svenska", # Swedish
- "sv-rAX": u"Svenska (Åland)", # Swedish (Åland Islands)
- "sv-rFI": u"Svenska (Finland)", # Swedish (Finland)
- "sv-rSE": u"Svenska (Sverige)", # Swedish (Sweden)
- "sw": u"Kiswahili", # Swahili
- "sw-rKE": u"Kiswahili (Kenya)", # Swahili (Kenya)
- "sw-rTZ": u"Kiswahili (Tanzania)", # Swahili (Tanzania)
- "sw-rUG": u"Kiswahili (Uganda)", # Swahili (Uganda)
- "swc": u"Kiswahili ya Kongo", # Congo Swahili
- "swc-rCD": u"Kiswahili ya Kongo (Jamhuri ya Kidemokrasia ya Kongo)", # Congo Swahili (Congo [DRC])
- "ta": u"தமிழ்", # Tamil
- "ta-rIN": u"தமிழ் (இந்தியா)", # Tamil (India)
- "ta-rLK": u"தமிழ் (இலங்கை)", # Tamil (Sri Lanka)
- "ta-rMY": u"தமிழ் (மலேஷியா)", # Tamil (Malaysia)
- "ta-rSG": u"தமிழ் (சிங்கப்பூர்)", # Tamil (Singapore)
- "te": u"తెలుగు", # Telugu
- "te-rIN": u"తెలుగు (భారత దేశం)", # Telugu (India)
- "teo": u"Kiteso", # Teso
- "teo-rKE": u"Kiteso (Kenia)", # Teso (Kenya)
- "teo-rUG": u"Kiteso (Uganda)", # Teso (Uganda)
- "th": u"ไทย", # Thai
- "th-rTH": u"ไทย (ไทย)", # Thai (Thailand)
- "ti": u"ትግርኛ", # Tigrinya
- "ti-rER": u"ትግርኛ (ER)", # Tigrinya (Eritrea)
- "ti-rET": u"ትግርኛ (ET)", # Tigrinya (Ethiopia)
- "to": u"Lea fakatonga", # Tongan
- "to-rTO": u"Lea fakatonga (Tonga)", # Tongan (Tonga)
- "tr": u"Türkçe", # Turkish
- "tr-rCY": u"Türkçe (Güney Kıbrıs Rum Kesimi)", # Turkish (Cyprus)
- "tr-rTR": u"Türkçe (Türkiye)", # Turkish (Turkey)
- "twq": u"Tasawaq senni", # Tasawaq
- "twq-rNE": u"Tasawaq senni (Nižer)", # Tasawaq (Niger)
- "tzm": u"Tamaziɣt", # Central Atlas Tamazight
- "tzm-rLATN": u"Tamaziɣt (LATN)", # Central Atlas Tamazight (LATN)
- "tzm-rLATN_MA": u"Tamaziɣt (Meṛṛuk,MA)", # Central Atlas Tamazight (Morocco,MA)
- "uk": u"українська", # Ukrainian
- "uk-rUA": u"українська (Україна)", # Ukrainian (Ukraine)
- "ur": u"اردو", # Urdu
- "ur-rIN": u"اردو (بھارت)", # Urdu (India)
- "ur-rPK": u"اردو (پاکستان)", # Urdu (Pakistan)
- "uz": u"Ўзбек", # Uzbek
- "uz-rARAB": u"اوزبیک (ARAB)", # Uzbek (ARAB)
- "uz-rARAB_AF": u"اوزبیک (افغانستان,AF)", # Uzbek (Afghanistan,AF)
- "uz-rCYRL": u"Ўзбек (CYRL)", # Uzbek (CYRL)
- "uz-rCYRL_UZ": u"Ўзбек (Ўзбекистон,UZ)", # Uzbek (Uzbekistan,UZ)
- "uz-rLATN": u"Oʻzbekcha (LATN)", # Uzbek (LATN)
- "uz-rLATN_UZ": u"Oʻzbekcha (Oʻzbekiston,UZ)", # Uzbek (Uzbekistan,UZ)
- "vai": u"ꕙꔤ", # Vai
- "vai-rLATN": u"Vai (LATN)", # Vai (LATN)
- "vai-rLATN_LR": u"Vai (Laibhiya,LR)", # Vai (Liberia,LR)
- "vai-rVAII": u"ꕙꔤ (VAII)", # Vai (VAII)
- "vai-rVAII_LR": u"ꕙꔤ (ꕞꔤꔫꕩ,LR)", # Vai (Liberia,LR)
- "vi": u"Tiếng Việt", # Vietnamese
- "vi-rVN": u"Tiếng Việt (Việt Nam)", # Vietnamese (Vietnam)
- "vun": u"Kyivunjo", # Vunjo
- "vun-rTZ": u"Kyivunjo (Tanzania)", # Vunjo (Tanzania)
- "xog": u"Olusoga", # Soga
- "xog-rUG": u"Olusoga (Yuganda)", # Soga (Uganda)
- "yav": u"Nuasue", # Yangben
- "yav-rCM": u"Nuasue (Kemelún)", # Yangben (Cameroon)
- "yo": u"Èdè Yorùbá", # Yoruba
- "yo-rNG": u"Èdè Yorùbá (Orílẹ́ède Nàìjíríà)", # Yoruba (Nigeria)
- # This was the obtained from Locale, but it seems it's different in Settings
- #"zh": u"中文", # Chinese
- "zh": u"中文 (简体)", # Chinese
- "zh-rHANS": u"中文 (HANS)", # Chinese (HANS)
- "zh-rHANS_CN": u"中文 (中国,CN)", # Chinese (China,CN)
- "zh-rHANS_HK": u"中文 (香港,HK)", # Chinese (Hong Kong,HK)
- "zh-rHANS_MO": u"中文 (澳门,MO)", # Chinese (Macau,MO)
- "zh-rHANS_SG": u"中文 (新加坡,SG)", # Chinese (Singapore,SG)
- "zh-rHANT": u"中文 (HANT)", # Chinese (HANT)
- "zh-rHANT_HK": u"中文 (香港,HK)", # Chinese (Hong Kong,HK)
- "zh-rHANT_MO": u"中文 (澳門,MO)", # Chinese (Macau,MO)
- "zh-rHANT_TW": u"中文 (台灣,TW)", # Chinese (Taiwan,TW)
- "zu": u"IsiZulu", # Zulu
- "zu-rZA": u"IsiZulu (iNingizimu Afrika)", # Zulu (South Africa)
- }
- if not languageTo in LANGUAGES.keys():
- raise RuntimeError("%s is not a supported language by AndroidViewClient" % languageTo)
- self.openQuickSettingsSettings()
- view = None
- currentLanguage = None
- ATTEMPTS = 10
- if self.vc.getSdkVersion() >= 20:
- for _ in range(ATTEMPTS):
- com_android_settings___id_dashboard = self.vc.findViewByIdOrRaise("com.android.settings:id/dashboard")
- for k, v in LANGUAGE_SETTINGS.iteritems():
- view = self.vc.findViewWithText(v, root=com_android_settings___id_dashboard)
- if view:
- currentLanguage = k
- break
- if view:
- break
- com_android_settings___id_dashboard.uiScrollable.flingForward()
- self.vc.sleep(1)
- self.vc.dump(-1)
- if view is None:
- raise ViewNotFoundException("text", "'Language & input' (any language)", "ROOT")
- view.touch()
- self.vc.sleep(1)
- self.vc.dump(-1)
- self.vc.findViewWithTextOrRaise(PHONE_LANGUAGE[currentLanguage]).touch()
- self.vc.sleep(1)
- self.vc.dump(-1)
- else:
- for _ in range(ATTEMPTS):
- android___id_list = self.vc.findViewByIdOrRaise("android:id/list")
- for k, v in LANGUAGE_SETTINGS.iteritems():
- view = self.vc.findViewWithText(v, root=android___id_list)
- if view:
- currentLanguage = k
- break
- if view:
- break
- android___id_list.uiScrollable.flingForward()
- self.vc.sleep(1)
- self.vc.dump(-1)
- if view is None:
- raise ViewNotFoundException("text", "'Language & input' (any language)", "ROOT")
- view.touch()
- self.vc.sleep(1)
- self.vc.dump(-1)
- self.vc.findViewWithTextOrRaise(PHONE_LANGUAGE[currentLanguage]).touch()
- self.vc.sleep(1)
- self.vc.dump(-1)
- android___id_list = self.vc.findViewByIdOrRaise("android:id/list")
- android___id_list.uiScrollable.setViewClient(self.vc)
- view = android___id_list.uiScrollable.scrollTextIntoView(LANGUAGES[languageTo])
- if view is not None:
- view.touch()
- else:
- #raise RuntimeError(u"Couldn't change language to %s (%s)" % (LANGUAGES[languageTo], languageTo))
- raise RuntimeError("Couldn't change language to %s" % languageTo)
- self.vc.device.press('BACK')
- self.vc.sleep(1)
- self.vc.device.press('BACK')
-
- class UiCollection():
- '''
- Used to enumerate a container's user interface (UI) elements for the purpose of counting, or
- targeting a sub elements by a child's text or description.
- '''
-
- pass
- class UiScrollable(UiCollection):
- '''
- A L{UiCollection} that supports searching for items in scrollable layout elements.
-
- This class can be used with horizontally or vertically scrollable controls.
- '''
- def __init__(self, view):
- self.vc = None
- self.view = view
- self.vertical = True
- self.bounds = view.getBounds()
- (self.x, self.y, self.w, self.h) = view.getPositionAndSize()
- self.steps = 10
- self.duration = 500
- self.swipeDeadZonePercentage = 0.1
- self.maxSearchSwipes = 10
-
- def flingBackward(self):
- if self.vertical:
- s = (self.x + self.w/2, self.y + self.h * self.swipeDeadZonePercentage)
- e = (self.x + self.w/2, self.y + self.h - self.h * self.swipeDeadZonePercentage)
- else:
- raise RuntimeError('Not implemented yet')
- if DEBUG:
- print >> sys.stderr, "flingBackward: view=", self.view.__smallStr__(), self.view.getPositionAndSize()
- print >> sys.stderr, "self.view.device.drag(%s, %s, %s, %s)" % (s, e, self.duration, self.steps)
- self.view.device.drag(s, e, self.duration, self.steps, self.view.device.display['orientation'])
-
- def flingForward(self):
- if self.vertical:
- s = (self.x + self.w/2, (self.y + self.h ) - self.h * self.swipeDeadZonePercentage)
- e = (self.x + self.w/2, self.y + self.h * self.swipeDeadZonePercentage)
- else:
- raise RuntimeError('Not implemented yet')
- if DEBUG:
- print >> sys.stderr, "flingForward: view=", self.view.__smallStr__(), self.view.getPositionAndSize()
- print >> sys.stderr, "self.view.device.drag(%s, %s, %s, %s)" % (s, e, self.duration, self.steps)
- self.view.device.drag(s, e, self.duration, self.steps, self.view.device.display['orientation'])
-
- def flingToBeginning(self, maxSwipes=10):
- if self.vertical:
- for _ in range(maxSwipes):
- if DEBUG:
- print >> sys.stderr, "flinging to beginning"
- self.flingBackward()
-
- def flingToEnd(self, maxSwipes=10):
- if self.vertical:
- for _ in range(maxSwipes):
- if DEBUG:
- print >> sys.stderr, "flinging to end"
- self.flingForward()
-
- def scrollTextIntoView(self, text):
- '''
- Performs a forward scroll action on the scrollable layout element until the text you provided is visible,
- or until swipe attempts have been exhausted. See setMaxSearchSwipes(int)
- '''
- if self.vc is None:
- raise ValueError('vc must be set in order to use this method')
- for n in range(self.maxSearchSwipes):
- # FIXME: now I need to figure out the best way of navigating to the ViewClient asossiated
- # with this UiScrollable.
- # It's using setViewClient() now.
- if DEBUG:
- for v in self.vc.views:
- try:
- print >> sys.stderr, " scrollTextIntoView: v=", v.getId(),
- print >> sys.stderr, v.getText()
- except:
- pass
- #v = self.vc.findViewWithText(text, root=self.view)
- v = self.vc.findViewWithText(text)
- if v is not None:
- return v
- self.flingForward()
- #self.vc.sleep(1)
- self.vc.dump(-1)
- # WARNING: after this dump, the value kept in self.view is outdated, it should be refreshed
- # in some way
- return None
- def setAsHorizontalList(self):
- self.vertical = False
-
- def setAsVerticalList(self):
- self.vertical = True
- def setMaxSearchSwipes(self, maxSwipes):
- self.maxSearchSwipes = maxSwipes
-
- def setViewClient(self, vc):
- self.vc = vc
-
- class ListView(View):
- '''
- ListView class.
- '''
- pass
- class UiAutomator2AndroidViewClient():
- '''
- UiAutomator XML to AndroidViewClient
- '''
- def __init__(self, device, version):
- self.device = device
- self.version = version
- self.root = None
- self.nodeStack = []
- self.parent = None
- self.views = []
- self.idCount = 1
- def StartElement(self, name, attributes):
- '''
- Expat start element event handler
- '''
- if name == 'hierarchy':
- pass
- elif name == 'node':
- # Instantiate an Element object
- attributes['uniqueId'] = 'id/no_id/%d' % self.idCount
- bounds = re.split('[\][,]', attributes['bounds'])
- attributes['bounds'] = ((int(bounds[1]), int(bounds[2])), (int(bounds[4]), int(bounds[5])))
- if DEBUG_BOUNDS:
- print >> sys.stderr, "bounds=", attributes['bounds']
- self.idCount += 1
- child = View.factory(attributes, self.device, self.version)
- self.views.append(child)
- # Push element onto the stack and make it a child of parent
- if not self.nodeStack:
- self.root = child
- else:
- self.parent = self.nodeStack[-1]
- self.parent.add(child)
- self.nodeStack.append(child)
- def EndElement(self, name):
- '''
- Expat end element event handler
- '''
- if name == 'hierarchy':
- pass
- elif name == 'node':
- self.nodeStack.pop()
- def CharacterData(self, data):
- '''
- Expat character data event handler
- '''
- if data.strip():
- data = data.encode()
- element = self.nodeStack[-1]
- element.cdata += data
- def Parse(self, uiautomatorxml):
- # Create an Expat parser
- parser = xml.parsers.expat.ParserCreate() # @UndefinedVariable
- # Set the Expat event handlers to our methods
- parser.StartElementHandler = self.StartElement
- parser.EndElementHandler = self.EndElement
- parser.CharacterDataHandler = self.CharacterData
- # Parse the XML File
- try:
- _ = parser.Parse(uiautomatorxml.encode(encoding='utf-8', errors='replace'), True)
- except xml.parsers.expat.ExpatError, ex: # @UndefinedVariable
- print >>sys.stderr, "ERROR: Offending XML:\n", repr(uiautomatorxml)
- raise RuntimeError(ex)
- return self.root
- class Excerpt2Code():
- ''' Excerpt XML to code '''
- def __init__(self):
- self.data = None
- def StartElement(self, name, attributes):
- '''
- Expat start element event handler
- '''
- if name == 'excerpt':
- pass
- else:
- warnings.warn("Unexpected element: '%s'" % name)
- def EndElement(self, name):
- '''
- Expat end element event handler
- '''
- if name == 'excerpt':
- pass
- def CharacterData(self, data):
- '''
- Expat character data event handler
- '''
- if data.strip():
- data = data.encode()
- if not self.data:
- self.data = data
- else:
- self.data += data
- def Parse(self, excerpt):
- # Create an Expat parser
- parser = xml.parsers.expat.ParserCreate() # @UndefinedVariable
- # Set the Expat event handlers to our methods
- parser.StartElementHandler = self.StartElement
- parser.EndElementHandler = self.EndElement
- parser.CharacterDataHandler = self.CharacterData
- # Parse the XML
- _ = parser.Parse(excerpt, 1)
- return self.data
- class ViewClientOptions:
- '''
- ViewClient options helper class
- '''
- DEVIDE = 'device'
- SERIALNO = 'serialno'
- AUTO_DUMP = 'autodump'
- FORCE_VIEW_SERVER_USE = 'forceviewserveruse'
- LOCAL_PORT = 'localport' # ViewServer local port
- REMOTE_PORT = 'remoteport' # ViewServer remote port
- START_VIEW_SERVER = 'startviewserver'
- IGNORE_UIAUTOMATOR_KILLED = 'ignoreuiautomatorkilled'
- COMPRESSED_DUMP = 'compresseddump'
-
- class ViewClient:
- '''
- ViewClient is a I{ViewServer} client.
- ViewServer backend
- ==================
- If not running the ViewServer is started on the target device or emulator and then the port
- mapping is created.
- UiAutomator backend
- ===================
- No service is started.
- '''
- imageDirectory = None
- ''' The directory used to store screenshot images '''
- 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):
- '''
- Constructor
- @type device: AdbClient
- @param device: The device running the C{View server} to which this client will connect
- @type serialno: str
- @param serialno: the serial number of the device or emulator to connect to
- @type adb: str
- @param adb: the path of the C{adb} executable or None and C{ViewClient} will try to find it
- @type autodump: boolean
- @param autodump: whether an automatic dump is performed at the end of this constructor
- @type forceviewserveruse: boolean
- @param forceviewserveruse: Force the use of C{ViewServer} even if the conditions to use
- C{UiAutomator} are satisfied
- @type localport: int
- @param localport: the local port used in the redirection
- @type remoteport: int
- @param remoteport: the remote port used to start the C{ViewServer} in the device or
- emulator
- @type startviewserver: boolean
- @param startviewserver: Whether to start the B{global} ViewServer
- @type ignoreuiautomatorkilled: boolean
- @param ignoreuiautomatorkilled: Ignores received B{Killed} message from C{uiautomator}
- @type compresseddump: boolean
- @param compresseddump: turns --compressed flag for uiautomator dump on/off
- '''
- if not device:
- raise Exception('Device is not connected')
- self.device = device
- ''' The C{AdbClient} device instance '''
- if not serialno:
- raise ValueError("Serialno cannot be None")
- self.serialno = self.__mapSerialNo(serialno)
- ''' The serial number of the device '''
- if DEBUG_DEVICE: print >> sys.stderr, "ViewClient: using device with serialno", self.serialno
- if adb:
- if not os.access(adb, os.X_OK):
- raise Exception('adb="%s" is not executable' % adb)
- else:
- # Using adbclient we don't need adb executable yet (maybe it's needed if we want to
- # start adb if not running)
- adb = obtainAdbPath()
- self.adb = adb
- ''' The adb command '''
- self.root = None
- ''' The root node '''
- self.viewsById = {}
- ''' The map containing all the L{View}s indexed by their L{View.getUniqueId()} '''
- self.display = {}
- ''' The map containing the device's display properties: width, height and density '''
- for prop in [ 'width', 'height', 'density', 'orientation' ]:
- self.display[prop] = -1
- if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES:
- try:
- self.display[prop] = device.display[prop]
- except:
- if WARNINGS:
- warnings.warn("Couldn't determine display %s" % prop)
- else:
- # these values are usually not defined as properties, so we stick to the -1 set
- # before
- pass
- self.build = {}
- ''' The map containing the device's build properties: version.sdk, version.release '''
- for prop in [VERSION_SDK_PROPERTY, VERSION_RELEASE_PROPERTY]:
- self.build[prop] = -1
- try:
- if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES:
- self.build[prop] = device.getProperty(prop)
- else:
- self.build[prop] = device.shell('getprop ro.build.' + prop)[:-2]
- except:
- if WARNINGS:
- warnings.warn("Couldn't determine build %s" % prop)
- if prop == VERSION_SDK_PROPERTY:
- # we expect it to be an int
- self.build[prop] = int(self.build[prop] if self.build[prop] else -1)
- self.ro = {}
- ''' The map containing the device's ro properties: secure, debuggable '''
- for prop in ['secure', 'debuggable']:
- try:
- self.ro[prop] = device.shell('getprop ro.' + prop)[:-2]
- except:
- if WARNINGS:
- warnings.warn("Couldn't determine ro %s" % prop)
- self.ro[prop] = 'UNKNOWN'
- self.forceViewServerUse = forceviewserveruse
- ''' Force the use of ViewServer even if the conditions to use UiAutomator are satisfied '''
- self.useUiAutomator = (self.build[VERSION_SDK_PROPERTY] >= 16) and not forceviewserveruse # jelly bean 4.1 & 4.2
- if DEBUG:
- print >> sys.stderr, " ViewClient.__init__: useUiAutomator=", self.useUiAutomator, "sdk=", self.build[VERSION_SDK_PROPERTY], "forceviewserveruse=", forceviewserveruse
- ''' If UIAutomator is supported by the device it will be used '''
- self.ignoreUiAutomatorKilled = ignoreuiautomatorkilled
- ''' On some devices (i.e. Nexus 7 running 4.2.2) uiautomator is killed just after generating
- the dump file. In many cases the file is already complete so we can ask to ignore the 'Killed'
- message by setting L{ignoreuiautomatorkilled} to C{True}.
- Changes in v2.3.21 that uses C{/dev/tty} instead of a file may have turned this variable
- unnecessary, however it has been kept for backward compatibility.
- '''
- if self.useUiAutomator:
- self.textProperty = TEXT_PROPERTY_UI_AUTOMATOR
- else:
- if self.build[VERSION_SDK_PROPERTY] <= 10:
- self.textProperty = TEXT_PROPERTY_API_10
- else:
- self.textProperty = TEXT_PROPERTY
- if startviewserver:
- if not self.serviceResponse(device.shell('service call window 3')):
- try:
- self.assertServiceResponse(device.shell('service call window 1 i32 %d' %
- remoteport))
- except:
- msg = 'Cannot start View server.\n' \
- 'This only works on emulator and devices running developer versions.\n' \
- 'Does hierarchyviewer work on your device?\n' \
- 'See https://github.com/dtmilano/AndroidViewClient/wiki/Secure-mode\n\n' \
- 'Device properties:\n' \
- ' ro.secure=%s\n' \
- ' ro.debuggable=%s\n' % (self.ro['secure'], self.ro['debuggable'])
- raise Exception(msg)
- self.localPort = localport
- self.remotePort = remoteport
- # FIXME: it seems there's no way of obtaining the serialno from the MonkeyDevice
- subprocess.check_call([self.adb, '-s', self.serialno, 'forward', 'tcp:%d' % self.localPort,
- 'tcp:%d' % self.remotePort])
- self.windows = None
- ''' The list of windows as obtained by L{ViewClient.list()} '''
- self.uiDevice = UiDevice(self)
- ''' The L{UiDevice} '''
- ''' The output of compressed dump is different than output of uncompressed one.
- If one requires uncompressed output, this option should be set to False
- '''
- self.compressedDump = compresseddump
- if autodump:
- self.dump()
- def __del__(self):
- # should clean up some things
- pass
- @staticmethod
- def __obtainAdbPath():
- return obtainAdbPath()
- @staticmethod
- def __mapSerialNo(serialno):
- serialno = serialno.strip()
- #ipRE = re.compile('^\d+\.\d+.\d+.\d+$')
- if IP_RE.match(serialno):
- if DEBUG_DEVICE: print >>sys.stderr, "ViewClient: adding default port to serialno", serialno, ADB_DEFAULT_PORT
- return serialno + ':%d' % ADB_DEFAULT_PORT
- ipPortRE = re.compile('^\d+\.\d+.\d+.\d+:\d+$')
- if ipPortRE.match(serialno):
- # nothing to map
- return serialno
- if re.search("[.*()+]", serialno):
- raise ValueError("Regular expression not supported as serialno in ViewClient. Found '%s'" % serialno)
- return serialno
- @staticmethod
- def __obtainDeviceSerialNumber(device):
- if DEBUG_DEVICE: print >>sys.stderr, "ViewClient: obtaining serial number for connected device"
- serialno = device.getProperty('ro.serialno')
- if not serialno:
- serialno = device.shell('getprop ro.serialno')
- if serialno:
- serialno = serialno[:-2]
- if not serialno:
- qemu = device.shell('getprop ro.kernel.qemu')
- if qemu:
- qemu = qemu[:-2]
- if qemu and int(qemu) == 1:
- # FIXME !!!!!
- # this must be calculated from somewhere, though using a fixed serialno for now
- warnings.warn("Running on emulator but no serial number was specified then 'emulator-5554' is used")
- serialno = 'emulator-5554'
- if not serialno:
- # If there's only one device connected get its serialno
- adb = ViewClient.__obtainAdbPath()
- if DEBUG_DEVICE: print >>sys.stderr, " using adb=%s" % adb
- s = subprocess.Popen([adb, 'get-serialno'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={}).communicate()[0][:-1]
- if s != 'unknown':
- serialno = s
- if DEBUG_DEVICE: print >>sys.stderr, " serialno=%s" % serialno
- if not serialno:
- warnings.warn("Couldn't obtain the serialno of the connected device")
- return serialno
- @staticmethod
- def setAlarm(timeout):
- osName = platform.system()
- if osName.startswith('Windows'): # alarm is not implemented in Windows
- return
- signal.alarm(timeout)
- @staticmethod
- def connectToDeviceOrExit(timeout=60, verbose=False, ignoresecuredevice=False, ignoreversioncheck=False, serialno=None):
- '''
- Connects to a device which serial number is obtained from the script arguments if available
- or using the default regex C{.*}.
- If the connection is not successful the script exits.
-
- History
- -------
- In MonkeyRunner times, this method was a way of overcoming one of its limitations.
- L{MonkeyRunner.waitForConnection()} returns a L{MonkeyDevice} even if the connection failed.
- Then, to detect this situation, C{device.wake()} is attempted and if it fails then it is
- assumed the previous connection failed.
- @type timeout: int
- @param timeout: timeout for the connection
- @type verbose: bool
- @param verbose: Verbose output
- @type ignoresecuredevice: bool
- @param ignoresecuredevice: Ignores the check for a secure device
- @type ignoreversioncheck: bool
- @param ignoreversioncheck: Ignores the check for a supported ADB version
- @type serialno: str
- @param serialno: The device or emulator serial number
- @return: the device and serialno used for the connection
- '''
- progname = os.path.basename(sys.argv[0])
- if serialno is None:
- # eat all the extra options the invoking script may have added
- args = sys.argv
- while len(args) > 1 and args[1][0] == '-':
- args.pop(1)
- serialno = args[1] if len(args) > 1 else \
- os.environ['ANDROID_SERIAL'] if os.environ.has_key('ANDROID_SERIAL') \
- else '.*'
- if IP_RE.match(serialno):
- # If matches an IP address format and port was not specified add the default
- serialno += ':%d' % ADB_DEFAULT_PORT
- if verbose:
- print >> sys.stderr, 'Connecting to a device with serialno=%s with a timeout of %d secs...' % \
- (serialno, timeout)
- ViewClient.setAlarm(timeout+5)
- device = adbclient.AdbClient(serialno, ignoreversioncheck=ignoreversioncheck)
- ViewClient.setAlarm(0)
- if verbose:
- print >> sys.stderr, 'Connected to device with serialno=%s' % serialno
- secure = device.getSystemProperty('ro.secure')
- debuggable = device.getSystemProperty('ro.debuggable')
- versionProperty = device.getProperty(VERSION_SDK_PROPERTY)
- if versionProperty:
- version = int(versionProperty)
- else:
- if verbose:
- print "Couldn't obtain device SDK version"
- version = -1
- # we are going to use UiAutomator for versions >= 16 that's why we ignore if the device
- # is secure if this is true
- if secure == '1' and debuggable == '0' and not ignoresecuredevice and version < 16:
- print >> sys.stderr, "%s: ERROR: Device is secure, AndroidViewClient won't work." % progname
- if verbose:
- print >> sys.stderr, " secure=%s debuggable=%s version=%d ignoresecuredevice=%s" % \
- (secure, debuggable, version, ignoresecuredevice)
- sys.exit(2)
- if re.search("[.*()+]", serialno) and not re.search("(\d{1,3}\.){3}\d{1,3}", serialno):
- # if a regex was used we have to determine the serialno used
- serialno = ViewClient.__obtainDeviceSerialNumber(device)
- if verbose:
- print >> sys.stderr, 'Actual device serialno=%s' % serialno
- return device, serialno
- @staticmethod
- def traverseShowClassIdAndText(view, extraInfo=None, noExtraInfo=None, extraAction=None):
- '''
- Shows the View class, id and text if available.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @type extraInfo: method
- @param extraInfo: the View method to add extra info
- @type noExtraInfo: bool
- @param noExtraInfo: Don't add extra info
- @type extraAction: method
- @param extraAction: An extra action to be invoked for every view
- @return: the string containing class, id, and text if available
- '''
- try:
- eis = ''
- if extraInfo:
- eis = extraInfo(view)
- if not eis and noExtraInfo:
- eis = noExtraInfo
- if eis:
- eis = ' {0}'.format(eis)
- if extraAction:
- extraAction(view)
- _str = unicode(view.getClass())
- _str += ' '
- _str += view.getId()
- _str += ' '
- _str += view.getText() if view.getText() else ''
- _str += eis
- return _str
- except Exception, e:
- import traceback
- return u'Exception in view=%s: %s:%s\n%s' % (view.__smallStr__(), sys.exc_info()[0].__name__, e, traceback.format_exc())
- @staticmethod
- def traverseShowClassIdTextAndUniqueId(view):
- '''
- Shows the View class, id, text if available and unique id.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: the string containing class, id, and text if available and unique Id
- '''
- return ViewClient.traverseShowClassIdAndText(view, View.getUniqueId)
- @staticmethod
- def traverseShowClassIdTextAndContentDescription(view):
- '''
- Shows the View class, id, text if available and content description.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: the string containing class, id, and text if available and the content description
- '''
- return ViewClient.traverseShowClassIdAndText(view, View.getContentDescription, 'NAF')
- @staticmethod
- def traverseShowClassIdTextAndTag(view):
- '''
- Shows the View class, id, text if available and tag.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: the string containing class, id, and text if available and tag
- '''
- return ViewClient.traverseShowClassIdAndText(view, View.getTag, None)
- @staticmethod
- def traverseShowClassIdTextContentDescriptionAndScreenshot(view):
- '''
- Shows the View class, id, text if available and unique id and takes the screenshot.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: the string containing class, id, and text if available and the content description
- '''
- return ViewClient.traverseShowClassIdAndText(view, View.getContentDescription, 'NAF', extraAction=ViewClient.writeViewImageToFileInDir)
- @staticmethod
- def traverseShowClassIdTextAndCenter(view):
- '''
- Shows the View class, id and text if available and center.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: the string containing class, id, and text if available
- '''
- return ViewClient.traverseShowClassIdAndText(view, View.getCenter)
- @staticmethod
- def traverseShowClassIdTextPositionAndSize(view):
- '''
- Shows the View class, id and text if available.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: the string containing class, id, and text if available
- '''
- return ViewClient.traverseShowClassIdAndText(view, View.getPositionAndSize)
- @staticmethod
- def traverseShowClassIdTextAndBounds(view):
- '''
- Shows the View class, id and text if available.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: the string containing class, id, and text if available plus
- View bounds
- '''
- return ViewClient.traverseShowClassIdAndText(view, View.getBounds)
- @staticmethod
- def traverseTakeScreenshot(view):
- '''
- Don't show any any, just takes the screenshot.
- This function can be used as a transform function to L{ViewClient.traverse()}
- @type view: I{View}
- @param view: the View
- @return: None
- '''
- return ViewClient.writeViewImageToFileInDir(view)
- # methods that can be used to transform ViewClient.traverse output
- TRAVERSE_CIT = traverseShowClassIdAndText
- ''' An alias for L{traverseShowClassIdAndText(view)} '''
- TRAVERSE_CITUI = traverseShowClassIdTextAndUniqueId
- ''' An alias for L{traverseShowClassIdTextAndUniqueId(view)} '''
- TRAVERSE_CITCD = traverseShowClassIdTextAndContentDescription
- ''' An alias for L{traverseShowClassIdTextAndContentDescription(view)} '''
- TRAVERSE_CITG = traverseShowClassIdTextAndTag
- ''' An alias for L{traverseShowClassIdTextAndTag(view)} '''
- TRAVERSE_CITC = traverseShowClassIdTextAndCenter
- ''' An alias for L{traverseShowClassIdTextAndCenter(view)} '''
- TRAVERSE_CITPS = traverseShowClassIdTextPositionAndSize
- ''' An alias for L{traverseShowClassIdTextPositionAndSize(view)} '''
- TRAVERSE_CITB = traverseShowClassIdTextAndBounds
- ''' An alias for L{traverseShowClassIdTextAndBounds(view)} '''
- TRAVERSE_CITCDS = traverseShowClassIdTextContentDescriptionAndScreenshot
- ''' An alias for L{traverseShowClassIdTextContentDescriptionAndScreenshot(view)} '''
- TRAVERSE_S = traverseTakeScreenshot
- ''' An alias for L{traverseTakeScreenshot(view)} '''
- @staticmethod
- def sleep(secs=1.0):
- '''
- Sleeps for the specified number of seconds.
- @type secs: float
- @param secs: number of seconds
- '''
- time.sleep(secs)
- def assertServiceResponse(self, response):
- '''
- Checks whether the response received from the server is correct or raises and Exception.
- @type response: str
- @param response: Response received from the server
- @raise Exception: If the response received from the server is invalid
- '''
- if not self.serviceResponse(response):
- raise Exception('Invalid response received from service.')
- def serviceResponse(self, response):
- '''
- Checks the response received from the I{ViewServer}.
- @return: C{True} if the response received matches L{PARCEL_TRUE}, C{False} otherwise
- '''
- PARCEL_TRUE = "Result: Parcel(00000000 00000001 '........')\r\n"
- ''' The TRUE response parcel '''
- if DEBUG:
- print >>sys.stderr, "serviceResponse: comparing '%s' vs Parcel(%s)" % (response, PARCEL_TRUE)
- return response == PARCEL_TRUE
- def setViews(self, received, windowId=None):
- '''
- Sets L{self.views} to the received value splitting it into lines.
- @type received: str
- @param received: the string received from the I{View Server}
- '''
- if not received or received == "":
- raise ValueError("received is empty")
- self.views = []
- ''' 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()}. '''
- self.__parseTree(received.split("\n"), windowId)
- if DEBUG:
- print >>sys.stderr, "there are %d views in this dump" % len(self.views)
- def setViewsFromUiAutomatorDump(self, received):
- '''
- Sets L{self.views} to the received value parsing the received XML.
- @type received: str
- @param received: the string received from the I{UI Automator}
- '''
- if not received or received == "":
- raise ValueError("received is empty")
- self.views = []
- ''' 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()}. '''
- self.__parseTreeFromUiAutomatorDump(received)
- if DEBUG:
- print >>sys.stderr, "there are %d views in this dump" % len(self.views)
- def __splitAttrs(self, strArgs):
- '''
- Splits the C{View} attributes in C{strArgs} and optionally adds the view id to the C{viewsById} list.
- Unique Ids
- ==========
- It is very common to find C{View}s having B{NO_ID} as the Id. This turns very difficult to
- use L{self.findViewById()}. To help in this situation this method assigns B{unique Ids}.
- The B{unique Ids} are generated using the pattern C{id/no_id/<number>} with C{<number>} starting
- at 1.
- @type strArgs: str
- @param strArgs: the string containing the raw list of attributes and values
- @return: Returns the attributes map.
- '''
- if self.useUiAutomator:
- raise RuntimeError("This method is not compatible with UIAutomator")
- # replace the spaces in text:mText to preserve them in later split
- # they are translated back after the attribute matches
- textRE = re.compile('%s=%s,' % (self.textProperty, _nd('len')))
- m = textRE.search(strArgs)
- if m:
- __textStart = m.end()
- __textLen = int(m.group('len'))
- __textEnd = m.end() + __textLen
- s1 = strArgs[__textStart:__textEnd]
- s2 = s1.replace(' ', WS)
- strArgs = strArgs.replace(s1, s2, 1)
- idRE = re.compile("(?P<viewId>id/\S+)")
- attrRE = re.compile('%s(?P<parens>\(\))?=%s,(?P<val>[^ ]*)' % (_ns('attr'), _nd('len')), flags=re.DOTALL)
- hashRE = re.compile('%s@%s' % (_ns('class'), _nh('oid')))
- attrs = {}
- viewId = None
- m = idRE.search(strArgs)
- if m:
- viewId = m.group('viewId')
- if DEBUG:
- print >>sys.stderr, "found view with id=%s" % viewId
- for attr in strArgs.split():
- m = attrRE.match(attr)
- if m:
- __attr = m.group('attr')
- __parens = '()' if m.group('parens') else ''
- __len = int(m.group('len'))
- __val = m.group('val')
- if WARNINGS and __len != len(__val):
- warnings.warn("Invalid len: expected: %d found: %d s=%s e=%s" % (__len, len(__val), __val[:50], __val[-50:]))
- if __attr == self.textProperty:
- # restore spaces that have been replaced
- __val = __val.replace(WS, ' ')
- attrs[__attr + __parens] = __val
- else:
- m = hashRE.match(attr)
- if m:
- attrs['class'] = m.group('class')
- attrs['oid'] = m.group('oid')
- else:
- if DEBUG:
- print >>sys.stderr, attr, "doesn't match"
- if True: # was assignViewById
- if not viewId:
- # If the view has NO_ID we are assigning a default id here (id/no_id) which is
- # immediately incremented if another view with no id was found before to generate
- # a unique id
- viewId = "id/no_id/1"
- if viewId in self.viewsById:
- # sometimes the view ids are not unique, so let's generate a unique id here
- i = 1
- while True:
- newId = re.sub('/\d+$', '', viewId) + '/%d' % i
- if not newId in self.viewsById:
- break
- i += 1
- viewId = newId
- if DEBUG:
- print >>sys.stderr, "adding viewById %s" % viewId
- # We are assigning a new attribute to keep the original id preserved, which could have
- # been NO_ID repeated multiple times
- attrs['uniqueId'] = viewId
- return attrs
- def __parseTree(self, receivedLines, windowId=None):
- '''
- Parses the View tree contained in L{receivedLines}. The tree is created and the root node assigned to L{self.root}.
- This method also assigns L{self.viewsById} values using L{View.getUniqueId} as the key.
- @type receivedLines: str
- @param receivedLines: the string received from B{View Server}
- '''
- self.root = None
- self.viewsById = {}
- self.views = []
- parent = None
- parents = []
- treeLevel = -1
- newLevel = -1
- lastView = None
- for v in receivedLines:
- if v == '' or v == 'DONE' or v == 'DONE.':
- break
- attrs = self.__splitAttrs(v)
- if not self.root:
- if v[0] == ' ':
- raise Exception("Unexpected root element starting with ' '.")
- self.root = View.factory(attrs, self.device, self.build[VERSION_SDK_PROPERTY], self.forceViewServerUse, windowId)
- if DEBUG: self.root.raw = v
- treeLevel = 0
- newLevel = 0
- lastView = self.root
- parent = self.root
- parents.append(parent)
- else:
- newLevel = (len(v) - len(v.lstrip()))
- if newLevel == 0:
- raise Exception("newLevel==0 treeLevel=%d but tree can have only one root, v=%s" % (treeLevel, v))
- child = View.factory(attrs, self.device, self.build[VERSION_SDK_PROPERTY], self.forceViewServerUse, windowId)
- if DEBUG: child.raw = v
- if newLevel == treeLevel:
- parent.add(child)
- lastView = child
- elif newLevel > treeLevel:
- if (newLevel - treeLevel) != 1:
- raise Exception("newLevel jumps %d levels, v=%s" % ((newLevel-treeLevel), v))
- parent = lastView
- parents.append(parent)
- parent.add(child)
- lastView = child
- treeLevel = newLevel
- else: # newLevel < treeLevel
- for _ in range(treeLevel - newLevel):
- parents.pop()
- parent = parents.pop()
- parents.append(parent)
- parent.add(child)
- treeLevel = newLevel
- lastView = child
- self.views.append(lastView)
- self.viewsById[lastView.getUniqueId()] = lastView
- def __parseTreeFromUiAutomatorDump(self, receivedXml):
- parser = UiAutomator2AndroidViewClient(self.device, self.build[VERSION_SDK_PROPERTY])
- try:
- start_xml_index = receivedXml.index("<")
- except ValueError:
- raise ValueError("received does not contain valid XML data")
- self.root = parser.Parse(receivedXml[start_xml_index:])
- self.views = parser.views
- self.viewsById = {}
- for v in self.views:
- self.viewsById[v.getUniqueId()] = v
- def getRoot(self):
- '''
- Gets the root node of the C{View} tree
- @return: the root node of the C{View} tree
- '''
- return self.root
- def traverse(self, root="ROOT", indent="", transform=None, stream=sys.stdout):
- '''
- Traverses the C{View} tree and prints its nodes.
- The nodes are printed converting them to string but other transformations can be specified
- by providing a method name as the C{transform} parameter.
- @type root: L{View}
- @param root: the root node from where the traverse starts
- @type indent: str
- @param indent: the indentation string to use to print the nodes
- @type transform: method
- @param transform: a method to use to transform the node before is printed
- '''
- if transform is None:
- # this cannot be a default value, otherwise
- # TypeError: 'staticmethod' object is not callable
- # is raised
- transform = ViewClient.TRAVERSE_CIT
- if type(root) == types.StringType and root == "ROOT":
- root = self.root
- return ViewClient.__traverse(root, indent, transform, stream)
- # if not root:
- # return
- #
- # s = transform(root)
- # if s:
- # print >>stream, "%s%s" % (indent, s)
- #
- # for ch in root.children:
- # self.traverse(ch, indent=indent+" ", transform=transform, stream=stream)
- @staticmethod
- def __traverse(root, indent="", transform=View.__str__, stream=sys.stdout):
- if not root:
- return
- s = transform(root)
- if stream and s:
- ius = "%s%s" % (indent, s if isinstance(s, unicode) else unicode(s, 'utf-8', 'replace'))
- print >>stream, ius.encode('utf-8', 'replace')
- for ch in root.children:
- ViewClient.__traverse(ch, indent=indent+" ", transform=transform, stream=stream)
- def dump(self, window=-1, sleep=1):
- '''
- Dumps the window content.
- Sleep is useful to wait some time before obtaining the new content when something in the
- window has changed.
- @type window: int or str
- @param window: the window id or name of the window to dump.
- The B{name} is the package name or the window name (i.e. StatusBar) for
- system windows.
- The window id can be provided as C{int} or C{str}. The C{str} should represent
- and C{int} in either base 10 or 16.
- Use -1 to dump all windows.
- This parameter only is used when the backend is B{ViewServer} and it's
- ignored for B{UiAutomator}.
- @type sleep: int
- @param sleep: sleep in seconds before proceeding to dump the content
- @return: the list of Views as C{str} received from the server after being split into lines
- '''
- if sleep > 0:
- time.sleep(sleep)
- if self.useUiAutomator:
- # NOTICE:
- # Using /dev/tty this works even on devices with no sdcard
- 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')
- if not received:
- raise RuntimeError('ERROR: Empty UiAutomator dump was received')
- if DEBUG:
- self.received = received
- if DEBUG_RECEIVED:
- print >>sys.stderr, "received %d chars" % len(received)
- print >>sys.stderr
- print >>sys.stderr, repr(received)
- print >>sys.stderr
- onlyKilledRE = re.compile('[\n\S]*Killed[\n\r\S]*', re.MULTILINE)
- if onlyKilledRE.search(received):
- MONKEY = 'com.android.commands.monkey'
- extraInfo = ''
- if self.device.shell('ps | grep "%s"' % MONKEY):
- extraInfo = "\nIt is know that '%s' conflicts with 'uiautomator'. Please kill it and try again." % MONKEY
- raise RuntimeError('''ERROR: UiAutomator output contains no valid information. UiAutomator was killed, no reason given.''' + extraInfo)
- if self.ignoreUiAutomatorKilled:
- if DEBUG_RECEIVED:
- print >>sys.stderr, "ignoring UiAutomator Killed"
- killedRE = re.compile('</hierarchy>[\n\S]*Killed', re.MULTILINE)
- if killedRE.search(received):
- received = re.sub(killedRE, '</hierarchy>', received)
- elif DEBUG_RECEIVED:
- print "UiAutomator Killed: NOT FOUND!"
- # It seems that API18 uiautomator spits this message to stdout
- dumpedToDevTtyRE = re.compile('</hierarchy>[\n\S]*UI hierchary dumped to: /dev/tty.*', re.MULTILINE)
- if dumpedToDevTtyRE.search(received):
- received = re.sub(dumpedToDevTtyRE, '</hierarchy>', received)
- if DEBUG_RECEIVED:
- print >>sys.stderr, "received=", received
- # API19 seems to send this warning as part of the XML.
- # Let's remove it if present
- received = received.replace('WARNING: linker: libdvm.so has text relocations. This is wasting memory and is a security risk. Please fix.\r\n', '')
- if re.search('\[: not found', received):
- raise RuntimeError('''ERROR: Some emulator images (i.e. android 4.1.2 API 16 generic_x86) does not include the '[' command.
- While UiAutomator back-end might be supported 'uiautomator' command fails.
- You should force ViewServer back-end.''')
- if received.startswith('ERROR: could not get idle state.'):
- # See https://android.googlesource.com/platform/frameworks/testing/+/jb-mr2-release/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
- raise RuntimeError('''The views are being refreshed too frequently to dump.''')
- self.setViewsFromUiAutomatorDump(received)
- else:
- if isinstance(window, str):
- if window != '-1':
- self.list(sleep=0)
- found = False
- for wId in self.windows:
- try:
- if window == self.windows[wId]:
- window = wId
- found = True
- break
- except:
- pass
- try:
- if int(window) == wId:
- window = wId
- found = True
- break
- except:
- pass
- try:
- if int(window, 16) == wId:
- window = wId
- found = True
- break
- except:
- pass
- if not found:
- raise RuntimeError("ERROR: Cannot find window '%s' in %s" % (window, self.windows))
- else:
- window = -1
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- try:
- s.connect((VIEW_SERVER_HOST, self.localPort))
- except socket.error, ex:
- raise RuntimeError("ERROR: Connecting to %s:%d: %s" % (VIEW_SERVER_HOST, self.localPort, ex))
- cmd = 'dump %x\r\n' % window
- if DEBUG:
- print >>sys.stderr, "executing: '%s'" % cmd
- s.send(cmd)
- received = ""
- doneRE = re.compile("DONE")
- ViewClient.setAlarm(120)
- while True:
- if DEBUG_RECEIVED:
- print >>sys.stderr, " reading from socket..."
- received += s.recv(1024)
- if doneRE.search(received[-7:]):
- break
- s.close()
- ViewClient.setAlarm(0)
- if DEBUG:
- self.received = received
- if DEBUG_RECEIVED:
- print >>sys.stderr, "received %d chars" % len(received)
- print >>sys.stderr
- print >>sys.stderr, received
- print >>sys.stderr
- if received:
- for c in received:
- if ord(c) > 127:
- received = unicode(received, encoding='utf-8', errors='replace')
- break
- self.setViews(received, hex(window)[2:])
- if DEBUG_TREE:
- self.traverse(self.root)
- return self.views
- def list(self, sleep=1):
- '''
- List the windows.
- Sleep is useful to wait some time before obtaining the new content when something in the
- window has changed.
- This also sets L{self.windows} as the list of windows.
- @type sleep: int
- @param sleep: sleep in seconds before proceeding to dump the content
- @return: the list of windows
- '''
- if sleep > 0:
- time.sleep(sleep)
- if self.useUiAutomator:
- raise Exception("Not implemented yet: listing windows with UiAutomator")
- else:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- try:
- s.connect((VIEW_SERVER_HOST, self.localPort))
- except socket.error, ex:
- raise RuntimeError("ERROR: Connecting to %s:%d: %s" % (VIEW_SERVER_HOST, self.localPort, ex))
- s.send('list\r\n')
- received = ""
- doneRE = re.compile("DONE")
- while True:
- received += s.recv(1024)
- if doneRE.search(received[-7:]):
- break
- s.close()
- if DEBUG:
- self.received = received
- if DEBUG_RECEIVED:
- print >>sys.stderr, "received %d chars" % len(received)
- print >>sys.stderr
- print >>sys.stderr, received
- print >>sys.stderr
- self.windows = {}
- for line in received.split('\n'):
- if not line:
- break
- if doneRE.search(line):
- break
- values = line.split()
- if len(values) > 1:
- package = values[1]
- else:
- package = "UNKNOWN"
- if len(values) > 0:
- wid = values[0]
- else:
- wid = '00000000'
- self.windows[int('0x' + wid, 16)] = package
- return self.windows
- def findViewById(self, viewId, root="ROOT", viewFilter=None):
- '''
- Finds the View with the specified viewId.
- @type viewId: str
- @param viewId: the ID of the view to find
- @type root: str
- @type root: View
- @param root: the root node of the tree where the View will be searched
- @type: viewFilter: function
- @param viewFilter: a function that will be invoked providing the candidate View as a parameter
- and depending on the return value (C{True} or C{False}) the View will be
- selected and returned as the result of C{findViewById()} or ignored.
- This can be C{None} and no extra filtering is applied.
- @return: the C{View} found or C{None}
- '''
- if not root:
- return None
- if type(root) == types.StringType and root == "ROOT":
- return self.findViewById(viewId, self.root, viewFilter)
- if root.getId() == viewId:
- if viewFilter:
- if viewFilter(root):
- return root
- else:
- return root
- if re.match('^id/no_id', viewId) or re.match('^id/.+/.+', viewId):
- if root.getUniqueId() == viewId:
- if viewFilter:
- if viewFilter(root):
- return root;
- else:
- return root
- for ch in root.children:
- foundView = self.findViewById(viewId, ch, viewFilter)
- if foundView:
- if viewFilter:
- if viewFilter(foundView):
- return foundView
- else:
- return foundView
- def findViewByIdOrRaise(self, viewId, root="ROOT", viewFilter=None):
- '''
- Finds the View or raise a ViewNotFoundException.
- @type viewId: str
- @param viewId: the ID of the view to find
- @type root: str
- @type root: View
- @param root: the root node of the tree where the View will be searched
- @type: viewFilter: function
- @param viewFilter: a function that will be invoked providing the candidate View as a parameter
- and depending on the return value (C{True} or C{False}) the View will be
- selected and returned as the result of C{findViewById()} or ignored.
- This can be C{None} and no extra filtering is applied.
- @return: the View found
- @raise ViewNotFoundException: raise the exception if View not found
- '''
- view = self.findViewById(viewId, root, viewFilter)
- if view:
- return view
- else:
- raise ViewNotFoundException("ID", viewId, root)
- def findViewByTag(self, tag, root="ROOT"):
- '''
- Finds the View with the specified tag
- '''
- return self.findViewWithAttribute('getTag()', tag, root)
- def findViewByTagOrRaise(self, tag, root="ROOT"):
- '''
- Finds the View with the specified tag or raise a ViewNotFoundException
- '''
- view = self.findViewWithAttribute('getTag()', tag, root)
- if view:
- return view
- else:
- raise ViewNotFoundException("tag", tag, root)
- def __findViewsWithAttributeInTree(self, attr, val, root):
- # Note the plural in this method name
- matchingViews = []
- if not self.root:
- print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
- return matchingViews
- if type(root) == types.StringType and root == "ROOT":
- root = self.root
- if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: type val=", type(val)
- if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: checking if root=%s has attr=%s == %s" % (root.__smallStr__(), attr, val)
- if root and attr in root.map and root.map[attr] == val:
- if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: FOUND: %s" % root.__smallStr__()
- matchingViews.append(root)
- else:
- for ch in root.children:
- matchingViews += self.__findViewsWithAttributeInTree(attr, val, ch)
- return matchingViews
- def __findViewWithAttributeInTree(self, attr, val, root):
- if DEBUG:
- print >> sys.stderr, " __findViewWithAttributeInTree: type(val)=", type(val)
- if type(val) != types.UnicodeType:
- u = unicode(val, encoding='utf-8', errors='ignore')
- else:
- u = val
- print >> sys.stderr, u'''__findViewWithAttributeInTree({0}'''.format(attr),
- try:
- print >> sys.stderr, u''', {0}'''.format(u),
- except:
- pass
- print >> sys.stderr, u'>>>>>>>>>>>>>>>>>>', type(root)
- if type(root) == types.StringType:
- print >> sys.stderr, u'>>>>>>>>>>>>>>>>>>', root
- print >> sys.stderr, u''', {0})'''.format(root)
- else:
- print >> sys.stderr, u''', {0})'''.format(root.__smallStr__())
- if not self.root:
- print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
- return None
- if type(root) == types.StringType and root == "ROOT":
- root = self.root
- if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: type val=", type(val)
- if DEBUG:
- #print >> sys.stderr, u'''__findViewWithAttributeInTree: checking if root={0}: '''.format(root),
- print >> sys.stderr, u'''has {0} == '''.format(attr),
- if type(val) == types.UnicodeType:
- u = val
- else:
- u = unicode(val, encoding='utf-8', errors='replace')
- try:
- print >> sys.stderr, u'''{0}'''.format(u)
- except:
- pass
- if isinstance(val, RegexType):
- return self.__findViewWithAttributeInTreeThatMatches(attr, val, root)
- else:
- try:
- if DEBUG:
- print >> sys.stderr, u'''__findViewWithAttributeInTree: comparing {0}: '''.format(attr),
- print >> sys.stderr, u'''{0} == '''.format(root.map[attr]),
- print >> sys.stderr, u'''{0}'''.format(val)
- except:
- pass
- if root and attr in root.map and root.map[attr] == val:
- if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: FOUND: %s" % root.__smallStr__()
- return root
- else:
- for ch in root.children:
- v = self.__findViewWithAttributeInTree(attr, val, ch)
- if v:
- return v
- return None
- def __findViewWithAttributeInTreeOrRaise(self, attr, val, root):
- view = self.__findViewWithAttributeInTree(attr, val, root)
- if view:
- return view
- else:
- raise ViewNotFoundException(attr, val, root)
- def __findViewWithAttributeInTreeThatMatches(self, attr, regex, root, rlist=[]):
- if not self.root:
- print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
- return None
- if type(root) == types.StringType and root == "ROOT":
- root = self.root
- if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTreeThatMatches: checking if root=%s attr=%s matches %s" % (root.__smallStr__(), attr, regex)
- if root and attr in root.map and regex.match(root.map[attr]):
- if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTreeThatMatches: FOUND: %s" % root.__smallStr__()
- return root
- #print >>sys.stderr, "appending root=%s to rlist=%s" % (root.__smallStr__(), rlist)
- #return rlist.append(root)
- else:
- for ch in root.children:
- v = self.__findViewWithAttributeInTreeThatMatches(attr, regex, ch, rlist)
- if v:
- return v
- #print >>sys.stderr, "appending v=%s to rlist=%s" % (v.__smallStr__(), rlist)
- #return rlist.append(v)
- return None
- #return rlist
- def findViewWithAttribute(self, attr, val, root="ROOT"):
- '''
- Finds the View with the specified attribute and value
- '''
- if DEBUG:
- try:
- print >> sys.stderr, u'findViewWithAttribute({0}, {1}, {2})'.format(attr, unicode(val, encoding='utf-8', errors='replace'), root)
- except:
- pass
- print >> sys.stderr, " findViewWithAttribute: type(val)=", type(val)
- return self.__findViewWithAttributeInTree(attr, val, root)
- def findViewsWithAttribute(self, attr, val, root="ROOT"):
- '''
- Finds the Views with the specified attribute and value.
- This allows you to see all items that match your criteria in the view hierarchy
- Usage:
- buttons = v.findViewsWithAttribute("class", "android.widget.Button")
- '''
- return self.__findViewsWithAttributeInTree(attr, val, root)
- def findViewWithAttributeOrRaise(self, attr, val, root="ROOT"):
- '''
- Finds the View or raise a ViewNotFoundException.
- @return: the View found
- @raise ViewNotFoundException: raise the exception if View not found
- '''
- view = self.findViewWithAttribute(attr, val, root)
- if view:
- return view
- else:
- raise ViewNotFoundException(attr, val, root)
- def findViewWithAttributeThatMatches(self, attr, regex, root="ROOT"):
- '''
- Finds the list of Views with the specified attribute matching
- regex
- '''
- return self.__findViewWithAttributeInTreeThatMatches(attr, regex, root)
- def findViewWithText(self, text, root="ROOT"):
- if DEBUG:
- try:
- print >>sys.stderr, '''findViewWithText({0}, {1})'''.format(text, root)
- print >> sys.stderr, " findViewWithText: type(text)=", type(text)
- except:
- pass
- if isinstance(text, RegexType):
- return self.findViewWithAttributeThatMatches(self.textProperty, text, root)
- #l = self.findViewWithAttributeThatMatches(TEXT_PROPERTY, text)
- #ll = len(l)
- #if ll == 0:
- # return None
- #elif ll == 1:
- # return l[0]
- #else:
- # print >>sys.stderr, "WARNING: findViewWithAttributeThatMatches invoked by findViewWithText returns %d items." % ll
- # return l
- else:
- return self.findViewWithAttribute(self.textProperty, text, root)
- def findViewWithTextOrRaise(self, text, root="ROOT"):
- '''
- Finds the View or raise a ViewNotFoundException.
- @return: the View found
- @raise ViewNotFoundException: raise the exception if View not found
- '''
- if DEBUG:
- print >>sys.stderr, "findViewWithTextOrRaise(%s, %s)" % (text, root)
- view = self.findViewWithText(text, root)
- if view:
- return view
- else:
- raise ViewNotFoundException("text", text, root)
- def findViewWithContentDescription(self, contentdescription, root="ROOT"):
- '''
- Finds the View with the specified content description
- '''
- return self.__findViewWithAttributeInTree('content-desc', contentdescription, root)
- def findViewWithContentDescriptionOrRaise(self, contentdescription, root="ROOT"):
- '''
- Finds the View with the specified content description
- '''
- return self.__findViewWithAttributeInTreeOrRaise('content-desc', contentdescription, root)
- def findViewsContainingPoint(self, (x, y), _filter=None):
- '''
- Finds the list of Views that contain the point (x, y).
- '''
- if not _filter:
- _filter = lambda v: True
- return [v for v in self.views if (v.containsPoint((x,y)) and _filter(v))]
- def getViewIds(self):
- '''
- @deprecated: Use L{getViewsById} instead.
- Returns the Views map.
- '''
- return self.viewsById
- def getViewsById(self):
- '''
- Returns the Views map. The keys are C{uniqueIds} and the values are C{View}s.
- '''
- return self.viewsById
- def __getFocusedWindowPosition(self):
- return self.__getFocusedWindowId()
- def getSdkVersion(self):
- '''
- Gets the SDK version.
- '''
- return self.build[VERSION_SDK_PROPERTY]
- def isKeyboardShown(self):
- '''
- Whether the keyboard is displayed.
- '''
- return self.device.isKeyboardShown()
- def writeImageToFile(self, filename, _format="PNG", deviceart=None, dropshadow=True, screenglare=True):
- '''
- Write the View image to the specified filename in the specified format.
- @type filename: str
- @param filename: Absolute path and optional filename receiving the image. If this points to
- a directory, then the filename is determined by the serialno of the device and
- format extension.
- @type _format: str
- @param _format: Image format (default format is PNG)
- '''
- filename = self.device.substituteDeviceTemplate(filename)
- if not os.path.isabs(filename):
- raise ValueError("writeImageToFile expects an absolute path (filename='%s')" % filename)
- if os.path.isdir(filename):
- filename = os.path.join(filename, self.serialno + '.' + _format.lower())
- if DEBUG:
- print >> sys.stderr, "writeImageToFile: saving image to '%s' in %s format (reconnect=%s)" % (filename, _format, self.device.reconnect)
- image = self.device.takeSnapshot(reconnect=self.device.reconnect)
- if deviceart:
- if 'STUDIO_DIR' in os.environ:
- PLUGIN_DIR = 'plugins/android/lib/device-art-resources'
- osName = platform.system()
- if osName == 'Darwin':
- deviceArtDir = os.environ['STUDIO_DIR'] + '/Contents/' + PLUGIN_DIR
- else:
- deviceArtDir = os.environ['STUDIO_DIR'] + '/' + PLUGIN_DIR
- # FIXME: should parse XML
- deviceArtXml = deviceArtDir + '/device-art.xml'
- if not os.path.exists(deviceArtXml):
- warnings.warn("Cannot find device art definition file")
- # <device id="nexus_5" name="Nexus 5">
- # <orientation name="port" size="1370,2405" screenPos="144,195" screenSize="1080,1920" shadow="port_shadow.png" back="port_back.png" lights="port_fore.png"/>
- # <orientation name="land" size="2497,1235" screenPos="261,65" screenSize="1920,1080" shadow="land_shadow.png" back="land_back.png" lights="land_fore.png"/>
- # </device>
- orientation = self.display['orientation']
- if orientation == 0 or orientation == 2:
- orientationName = 'port'
- elif orientation == 1 or orientation == 3:
- orientationName = 'land'
- else:
- warnings.warn("Unknown orientation=" + orientation)
- orientationName = 'port'
- separator = '_'
- if deviceart == 'auto':
- hardware = self.device.getProperty('ro.hardware')
- if hardware == 'hammerhead':
- deviceart = 'nexus_5'
- elif hardware == 'mako':
- deviceart = 'nexus_4'
- elif hardware == 'grouper':
- deviceart = 'nexus_7' # 2012
- elif hardware == 'mt5861':
- deviceart = 'tv_1080p'
- if deviceart == 'nexus_5':
- if orientationName == 'port':
- screenPos = (144, 195)
- else:
- screenPos = (261, 65)
- elif deviceart == 'nexus_4':
- if orientationName == 'port':
- screenPos = (94, 187)
- else:
- screenPos = (257, 45)
- elif deviceart == 'nexus_7': # 2012
- if orientationName == 'port':
- screenPos = (142, 190)
- else:
- screenPos = (260, 105)
- elif deviceart == 'tv_1080p':
- screenPos = (85, 59)
- orientationName = ''
- separator = ''
- SUPPORTED_DEVICES = ['nexus_5', 'nexus_4', 'nexus_7', 'tv_1080p']
- if deviceart not in SUPPORTED_DEVICES:
- warnings.warn("Only %s is supported now, more devices coming soon" % SUPPORTED_DEVICES)
- if not os.path.isdir(deviceArtDir + '/' + deviceart):
- warnings.warn("Cannot find device art for " + deviceart + ' at ' + deviceArtDir + '/' + deviceart)
- deviceArtModelDir = deviceArtDir + '/' + deviceart
- try:
- from PIL import Image
- if dropshadow:
- dropShadowImage = Image.open(deviceArtModelDir + '/%s%sshadow.png' % (orientationName, separator))
- deviceBack = Image.open(deviceArtModelDir + '/%s%sback.png' % (orientationName, separator))
- if dropshadow:
- dropShadowImage.paste(deviceBack, (0, 0), deviceBack)
- deviceBack = dropShadowImage
- deviceBack.paste(image, screenPos)
- if screenglare:
- screenGlareImage = Image.open(deviceArtModelDir + '/%s%sfore.png' % (orientationName, separator))
- deviceBack.paste(screenGlareImage, (0, 0), screenGlareImage)
- image = deviceBack
- except ImportError as ex:
- warnings.warn('''PIL or Pillow is needed for image manipulation
- On Ubuntu install
- $ sudo apt-get install python-imaging python-imaging-tk
- On OSX install
- $ brew install homebrew/python/pillow
- ''')
- else:
- warnings.warn("ViewClient.writeImageToFile: Cannot add device art because STUDIO_DIR environment variable was not set")
- image.save(filename, _format)
- @staticmethod
- def writeViewImageToFileInDir(view):
- '''
- Write the View image to the directory specified in C{ViewClient.imageDirectory}.
- @type view: View
- @param view: The view
- '''
- if not ViewClient.imageDirectory:
- raise RuntimeError('You must set ViewClient.imageDiretory in order to use this method')
- view.writeImageToFile(ViewClient.imageDirectory)
- @staticmethod
- def __pickleable(tree):
- '''
- Makes the tree pickleable.
- '''
- def removeDeviceReference(view):
- '''
- Removes the reference to a L{MonkeyDevice}.
- '''
- view.device = None
- ###########################################################################################
- # FIXME: Unfortunatelly deepcopy does not work with MonkeyDevice objects, which is
- # sadly the reason why we cannot pickle the tree and we need to remove the MonkeyDevice
- # references.
- # We wanted to copy the tree to preserve the original and make piclkleable the copy.
- #treeCopy = copy.deepcopy(tree)
- treeCopy = tree
- # IMPORTANT:
- # This assumes that the first element in the list is the tree root
- ViewClient.__traverse(treeCopy[0], transform=removeDeviceReference)
- ###########################################################################################
- return treeCopy
- def distanceTo(self, tree):
- '''
- Calculates the distance between the current state and the tree passed as argument.
- @type tree: list of Views
- @param tree: Tree of Views
- @return: the distance
- '''
- return ViewClient.distance(ViewClient.__pickleable(self.views), tree)
- @staticmethod
- def distance(tree1, tree2):
- '''
- Calculates the distance between the two trees.
- @type tree1: list of Views
- @param tree1: Tree of Views
- @type tree2: list of Views
- @param tree2: Tree of Views
- @return: the distance
- '''
- ################################################################
- #FIXME: this should copy the entire tree and then transform it #
- ################################################################
- pickleableTree1 = ViewClient.__pickleable(tree1)
- pickleableTree2 = ViewClient.__pickleable(tree2)
- s1 = pickle.dumps(pickleableTree1)
- s2 = pickle.dumps(pickleableTree2)
- if DEBUG_DISTANCE:
- print >>sys.stderr, "distance: calculating distance between", s1[:20], "and", s2[:20]
- l1 = len(s1)
- l2 = len(s2)
- t = float(max(l1, l2))
- if l1 == l2:
- if DEBUG_DISTANCE:
- print >>sys.stderr, "distance: trees have same length, using Hamming distance"
- return ViewClient.__hammingDistance(s1, s2)/t
- else:
- if DEBUG_DISTANCE:
- print >>sys.stderr, "distance: trees have different length, using Levenshtein distance"
- return ViewClient.__levenshteinDistance(s1, s2)/t
-
- @staticmethod
- def __hammingDistance(s1, s2):
- '''
- Finds the Hamming distance between two strings.
- @param s1: string
- @param s2: string
- @return: the distance
- @raise ValueError: if the lenght of the strings differ
- '''
- l1 = len(s1)
- l2 = len(s2)
- if l1 != l2:
- raise ValueError("Hamming distance requires strings of same size.")
- return sum(ch1 != ch2 for ch1, ch2 in zip(s1, s2))
- def hammingDistance(self, tree):
- '''
- Finds the Hamming distance between this tree and the one passed as argument.
- '''
- s1 = ' '.join(map(View.__str__, self.views))
- s2 = ' '.join(map(View.__str__, tree))
- return ViewClient.__hammingDistance(s1, s2)
- @staticmethod
- def __levenshteinDistance(s, t):
- '''
- Find the Levenshtein distance between two Strings.
- Python version of Levenshtein distance method implemented in Java at
- U{http://www.java2s.com/Code/Java/Data-Type/FindtheLevenshteindistancebetweentwoStrings.htm}.
- This is the number of changes needed to change one String into
- another, where each change is a single character modification (deletion,
- insertion or substitution).
- The previous implementation of the Levenshtein distance algorithm
- was from U{http://www.merriampark.com/ld.htm}
- Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError
- which can occur when my Java implementation is used with very large strings.
- This implementation of the Levenshtein distance algorithm
- is from U{http://www.merriampark.com/ldjava.htm}::
- StringUtils.getLevenshteinDistance(null, *) = IllegalArgumentException
- StringUtils.getLevenshteinDistance(*, null) = IllegalArgumentException
- StringUtils.getLevenshteinDistance("","") = 0
- StringUtils.getLevenshteinDistance("","a") = 1
- StringUtils.getLevenshteinDistance("aaapppp", "") = 7
- StringUtils.getLevenshteinDistance("frog", "fog") = 1
- StringUtils.getLevenshteinDistance("fly", "ant") = 3
- StringUtils.getLevenshteinDistance("elephant", "hippo") = 7
- StringUtils.getLevenshteinDistance("hippo", "elephant") = 7
- StringUtils.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
- StringUtils.getLevenshteinDistance("hello", "hallo") = 1
- @param s: the first String, must not be null
- @param t: the second String, must not be null
- @return: result distance
- @raise ValueError: if either String input C{null}
- '''
- if s is None or t is None:
- raise ValueError("Strings must not be null")
- n = len(s)
- m = len(t)
- if n == 0:
- return m
- elif m == 0:
- return n
- if n > m:
- tmp = s
- s = t
- t = tmp
- n = m;
- m = len(t)
- p = [None]*(n+1)
- d = [None]*(n+1)
- for i in range(0, n+1):
- p[i] = i
- for j in range(1, m+1):
- if DEBUG_DISTANCE:
- if j % 100 == 0:
- print >>sys.stderr, "DEBUG:", int(j/(m+1.0)*100),"%\r",
- t_j = t[j-1]
- d[0] = j
- for i in range(1, n+1):
- cost = 0 if s[i-1] == t_j else 1
- # minimum of cell to the left+1, to the top+1, diagonally left and up +cost
- d[i] = min(min(d[i-1]+1, p[i]+1), p[i-1]+cost)
- _d = p
- p = d
- d = _d
- if DEBUG_DISTANCE:
- print >> sys.stderr, "\n"
- return p[n]
- def levenshteinDistance(self, tree):
- '''
- Finds the Levenshtein distance between this tree and the one passed as argument.
- '''
- s1 = ' '.join(map(View.__microStr__, self.views))
- s2 = ' '.join(map(View.__microStr__, tree))
- return ViewClient.__levenshteinDistance(s1, s2)
- @staticmethod
- def excerpt(_str, execute=False):
- code = Excerpt2Code().Parse(_str)
- if execute:
- exec code
- else:
- return code
- class ConnectedDevice:
- def __init__(self, device, vc, serialno):
- self.device = device
- self.vc = vc
- self.serialno = serialno
- class CulebraOptions:
- '''
- Culebra options helper class
- '''
- HELP = 'help'
- VERBOSE = 'verbose'
- VERSION = 'version'
- IGNORE_SECURE_DEVICE = 'ignore-secure-device'
- IGNORE_VERSION_CHECK = 'ignore-version-check'
- FORCE_VIEW_SERVER_USE = 'force-view-server-use'
- DO_NOT_START_VIEW_SERVER = 'do-not-start-view-server'
- DO_NOT_IGNORE_UIAUTOMATOR_KILLED = 'do-not-ignore-uiautomator-killed'
- FIND_VIEWS_BY_ID = 'find-views-by-id'
- FIND_VIEWS_WITH_TEXT = 'find-views-with-text'
- FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 'find-views-with-content-description'
- USE_REGEXPS = 'use-regexps'
- VERBOSE_COMMENTS = 'verbose-comments'
- UNIT_TEST_CLASS = 'unit-test-class'
- UNIT_TEST_METHOD = 'unit-test-method'
- USE_JAR = 'use-jar'
- USE_DICTIONARY = 'use-dictionary'
- DICTIONARY_KEYS_FROM = 'dictionary-keys-from'
- AUTO_REGEXPS = 'auto-regexps'
- START_ACTIVITY = 'start-activity'
- OUTPUT = 'output'
- INTERACTIVE = 'interactive'
- WINDOW = 'window'
- APPEND_TO_SYS_PATH = 'append-to-sys-path'
- PREPEND_TO_SYS_PATH = 'prepend-to-sys-path'
- SAVE_SCREENSHOT = 'save-screenshot'
- SAVE_VIEW_SCREENSHOTS = 'save-view-screenshots'
- GUI = 'gui'
- SCALE = 'scale'
- DO_NOT_VERIFY_SCREEN_DUMP = 'do-not-verify-screen-dump'
- ORIENTATION_LOCKED = 'orientation-locked'
- SERIALNO = 'serialno'
- MULTI_DEVICE = 'multi-device'
- LOG_ACTIONS = 'log-actions'
- DEVICE_ART = 'device-art'
- DROP_SHADOW = 'drop-shadow'
- SCREEN_GLARE = 'glare'
- SHORT_OPTS = 'HVvIEFSkw:i:t:d:rCUM:j:D:K:R:a:o:pf:W:GuP:Os:mLA:ZB'
- LONG_OPTS = [HELP, VERBOSE, VERSION, IGNORE_SECURE_DEVICE, IGNORE_VERSION_CHECK, FORCE_VIEW_SERVER_USE,
- DO_NOT_START_VIEW_SERVER,
- DO_NOT_IGNORE_UIAUTOMATOR_KILLED,
- WINDOW + '=',
- FIND_VIEWS_BY_ID + '=', FIND_VIEWS_WITH_TEXT + '=', FIND_VIEWS_WITH_CONTENT_DESCRIPTION + '=',
- USE_REGEXPS, VERBOSE_COMMENTS, UNIT_TEST_CLASS, UNIT_TEST_METHOD + '=',
- USE_JAR + '=', USE_DICTIONARY + '=', DICTIONARY_KEYS_FROM + '=', AUTO_REGEXPS + '=',
- START_ACTIVITY + '=',
- OUTPUT + '=', PREPEND_TO_SYS_PATH,
- SAVE_SCREENSHOT + '=', SAVE_VIEW_SCREENSHOTS + '=',
- GUI,
- DO_NOT_VERIFY_SCREEN_DUMP,
- SCALE + '=',
- ORIENTATION_LOCKED,
- SERIALNO + '=',
- MULTI_DEVICE,
- LOG_ACTIONS,
- DEVICE_ART + '=', DROP_SHADOW, SCREEN_GLARE,
- ]
- LONG_OPTS_ARG = {WINDOW: 'WINDOW',
- FIND_VIEWS_BY_ID: 'BOOL', FIND_VIEWS_WITH_TEXT: 'BOOL', FIND_VIEWS_WITH_CONTENT_DESCRIPTION: 'BOOL',
- USE_JAR: 'BOOL', USE_DICTIONARY: 'BOOL', DICTIONARY_KEYS_FROM: 'VALUE', AUTO_REGEXPS: 'LIST',
- START_ACTIVITY: 'COMPONENT',
- OUTPUT: 'FILENAME',
- SAVE_SCREENSHOT: 'FILENAME', SAVE_VIEW_SCREENSHOTS: 'DIR',
- UNIT_TEST_METHOD: 'NAME',
- SCALE: 'FLOAT',
- SERIALNO: 'LIST',
- DEVICE_ART: 'MODEL'}
- OPTS_HELP = {
- 'H': 'prints this help',
- 'V': 'verbose comments',
- 'v': 'prints version number and exists',
- 'k': 'don\'t ignore UiAutomator killed',
- 'w': 'use WINDOW content (default: -1, all windows)',
- 'i': 'whether to use findViewById() in script',
- 't': 'whether to use findViewWithText() in script',
- 'd': 'whether to use findViewWithContentDescription',
- 'r': 'use regexps in matches',
- 'U': 'generates unit test class and script',
- 'M': 'generates unit test method. Can be used with or without -U',
- 'j': 'use jar and appropriate shebang to run script (deprecated)',
- 'D': 'use a dictionary to store the Views found',
- 'K': 'dictionary keys from: id, text, content-description',
- 'R': 'auto regexps (i.e. clock), implies -r. help list options',
- 'a': 'starts Activity before dump',
- 'o': 'output filename',
- 'p': 'prepend environment variables values to sys.path',
- 'f': 'save screenshot to file',
- 'W': 'save View screenshots to files in directory',
- 'E': 'ignores ADB version check',
- 'G': 'presents the GUI (EXPERIMENTAL)',
- 'P': 'scale percentage (i.e. 0.5)',
- 'u': 'do not verify screen state after dump',
- 'O': 'orientation locked in generated test',
- 's': 'device serial number (can be more than 1)',
- 'm': 'enables multi-device test generation',
- 'L': 'log actions using logcat',
- 'A': 'device art model to frame screenshot (auto: autodetected)',
- 'Z': 'drop shadow for device art screenshot',
- 'B': 'screen glare over screenshot',
- }
- class CulebraTestCase(unittest.TestCase):
- '''
- The base class for all CulebraTests.
-
- Class variables
- ---------------
- There are some class variables that can be used to change the behavior of the tests.
-
- B{serialno}: The serial number of the device. This can also be a list of devices for I{mutli-devices}
- tests or the keyword C{all} to run the tests on all available devices or C{default} to run the tests
- only on the default (first) device.
-
- When a I{multi-device} test is running the available devices are available in a list named
- L{self.devices} which has the corresponding L{ConnectedDevices} entries.
-
- Also, in the case of I{multi-devices} tests and to be backward compatible with I{single-device} tests
- the default device, the first one in the devices list, is assigned to L{self.device}, L{self.vc} and
- L{self.serialno} too.
-
- B{verbose}: The verbosity of the tests. This can be changed from the test command line using the
- command line option C{-v} or C{--verbose}.
- '''
-
- kwargs1 = None
- kwargs2 = None
- devices = None
- ''' The list of connected devices '''
- defaultDevice = None
- ''' The default L{ConnectedDevice}. Set to the first one found for multi-device cases '''
- serialno = None
- ''' The default connected device C{serialno} '''
- device = None
- ''' The default connected device '''
- vc = None
- ''' The default connected device C{ViewClient} '''
- verbose = False
- options = {}
- @classmethod
- def setUpClass(cls):
- cls.kwargs1 = {'ignoreversioncheck': False, 'verbose': False, 'ignoresecuredevice': False}
- cls.kwargs2 = {'startviewserver': True, 'forceviewserveruse': False, 'autodump': False, 'ignoreuiautomatorkilled': True}
- def __init__(self, methodName='runTest'):
- self.Log = CulebraTestCase.__Log(self)
- unittest.TestCase.__init__(self, methodName=methodName)
-
- def setUp(self):
- __devices = None
- if self.serialno:
- # serialno can be 1 serialno, multiple serialnos, 'all' or 'default'
- if self.serialno.lower() == 'all':
- __devices = [d.serialno for d in adbclient.AdbClient().getDevices()]
- elif self.serialno.lower() == 'default':
- __devices = [adbclient.AdbClient().getDevices()[0].serialno]
- else:
- __devices = self.serialno.split()
- if len(__devices) > 1:
- self.devices = __devices
- # FIXME: both cases should be unified
- if self.devices:
- __devices = self.devices
- self.devices = []
- for serialno in __devices:
- device, serialno = ViewClient.connectToDeviceOrExit(serialno=serialno, **self.kwargs1)
- if self.options[CulebraOptions.START_ACTIVITY]:
- device.startActivity(component=self.options[CulebraOptions.START_ACTIVITY])
- vc = ViewClient(device, serialno, **self.kwargs2)
- self.devices.append(ConnectedDevice(serialno=serialno, device=device, vc=vc))
- # Select the first devices as default
- self.defaultDevice = self.devices[0]
- self.device = self.defaultDevice.device
- self.serialno = self.defaultDevice.serialno
- self.vc = self.defaultDevice.vc
- else:
- self.devices = []
- if __devices:
- # A list containing only one device was specified
- self.serialno = __devices[0]
- self.device, self.serialno = ViewClient.connectToDeviceOrExit(serialno=self.serialno, **self.kwargs1)
- if self.options[CulebraOptions.START_ACTIVITY]:
- self.device.startActivity(component=self.options[CulebraOptions.START_ACTIVITY])
- self.vc = ViewClient(self.device, self.serialno, **self.kwargs2)
- # Set the default device, to be consistent with multi-devices case
- self.devices.append(ConnectedDevice(serialno=self.serialno, device=self.device, vc=self.vc))
- def tearDown(self):
- pass
- def preconditions(self):
- if self.options[CulebraOptions.ORIENTATION_LOCKED] is not None:
- # If orientation locked was set to a valid orientation value then use it to compare
- # against current orientation (when the test is run)
- return (self.device.display['orientation'] == self.options[CulebraOptions.ORIENTATION_LOCKED])
- return True
- def isTestRunningOnMultipleDevices(self):
- return (len(self.devices) > 1)
- @staticmethod
- def __passAll(arg):
- return True
- def all(self, arg, _filter=None):
- # CulebraTestCase.__passAll cannot be specified as the default argument value
- if _filter is None:
- _filter = CulebraTestCase.__passAll
- if DEBUG_MULTI:
- print >> sys.stderr, "all(%s, %s)" % (arg, _filter)
- l = (getattr(d, arg) for d in self.devices)
- for i in l:
- print >> sys.stderr, " i=", i
- return filter(_filter, (getattr(d, arg) for d in self.devices))
- def allVcs(self, _filter=None):
- return self.all('vc', _filter)
- def allDevices(self, _filter=None):
- return self.all('device', _filter)
- def allSerialnos(self, _filter=None):
- return self.all('serialno', _filter)
- def log(self, message, priority='D'):
- '''
- Logs a message with the specified priority.
- '''
-
- self.device.log('CULEBRA', message, priority, CulebraTestCase.verbose)
-
- class __Log():
- '''
- Log class to simulate C{android.util.Log}
- '''
- def __init__(self, culebraTestCase):
- self.culebraTestCase = culebraTestCase
-
- def __getattr__(self, attr):
- '''
- Returns the corresponding log method or @C{AttributeError}.
- '''
-
- if attr in ['v', 'd', 'i', 'w', 'e']:
- return lambda message: self.culebraTestCase.log(message, priority=attr.upper())
- raise AttributeError(self.__class__.__name__ + ' has no attribute "%s"' % attr)
-
- @staticmethod
- def main():
- # If you want to specify tests classes and methods in the command line you will be forced
- # to include -s or --serialno and the serial number of the device (could be a regexp)
- # as ViewClient would have no way of determine what it is.
- # This could be also a list of devices (delimited by whitespaces) and in such case all of
- # them will be used.
- # The special argument 'all' means all the connected devices.
- ser = ['-s', '--serialno']
- old = '%(failfast)'
- new = ' %s s The serial number[s] to connect to or \'all\'\n%s' % (', '.join(ser), old)
- unittest.TestProgram.USAGE = unittest.TestProgram.USAGE.replace(old, new)
- argsToRemove = []
- i = 0
- while i < len(sys.argv):
- a = sys.argv[i]
- if a in ['-v', '--verbose']:
- # make CulebraTestCase.verbose the same as unittest verbose
- CulebraTestCase.verbose = True
- elif a in ser:
- # remove arguments not handled by unittest
- if len(sys.argv) > (i+1):
- argsToRemove.append(sys.argv[i])
- CulebraTestCase.serialno = sys.argv[i+1]
- argsToRemove.append(CulebraTestCase.serialno)
- i += 1
- else:
- raise RuntimeError('serial number missing')
- i += 1
- for a in argsToRemove:
- sys.argv.remove(a)
- unittest.main()
- if __name__ == "__main__":
- try:
- vc = ViewClient(None)
- except:
- print "%s: Don't expect this to do anything" % __file__
|