adbclient.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  1. '''
  2. Copyright (C) 2012-2015 Diego Torres Milano
  3. Created on Dec 1, 2012
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. @author: Diego Torres Milano
  14. '''
  15. __version__ = '10.3.0'
  16. import sys
  17. import warnings
  18. import string
  19. import datetime
  20. if sys.executable:
  21. if 'monkeyrunner' in sys.executable:
  22. warnings.warn(
  23. '''
  24. You should use a 'python' interpreter, not 'monkeyrunner' for this module
  25. ''', RuntimeWarning)
  26. import socket
  27. import time
  28. import re
  29. import signal
  30. import os
  31. import types
  32. import platform
  33. from com.dtmilano.android.window import Window
  34. from com.dtmilano.android.common import _nd, _nh, _ns, obtainPxPy, obtainVxVy,\
  35. obtainVwVh
  36. from com.dtmilano.android.adb.androidkeymap import KEY_MAP
  37. DEBUG = False
  38. DEBUG_TOUCH = DEBUG and False
  39. DEBUG_LOG = DEBUG and False
  40. DEBUG_WINDOWS = DEBUG and False
  41. DEBUG_COORDS = DEBUG and False
  42. try:
  43. HOSTNAME = os.environ['ANDROID_ADB_SERVER_HOST']
  44. except:
  45. HOSTNAME = 'localhost'
  46. try:
  47. PORT = int(os.environ['ANDROID_ADB_SERVER_PORT'])
  48. except KeyError:
  49. PORT = 5037
  50. OKAY = 'OKAY'
  51. FAIL = 'FAIL'
  52. UP = 0
  53. DOWN = 1
  54. DOWN_AND_UP = 2
  55. TIMEOUT = 15
  56. WIFI_SERVICE = 'wifi'
  57. # some device properties
  58. VERSION_SDK_PROPERTY = 'ro.build.version.sdk'
  59. VERSION_RELEASE_PROPERTY = 'ro.build.version.release'
  60. class Device:
  61. @staticmethod
  62. def factory(_str):
  63. if DEBUG:
  64. print >> sys.stderr, "Device.factory(", _str, ")"
  65. values = _str.split(None, 2)
  66. if DEBUG:
  67. print >> sys.stderr, "values=", values
  68. return Device(*values)
  69. def __init__(self, serialno, status, qualifiers=None):
  70. self.serialno = serialno
  71. self.status = status
  72. self.qualifiers = qualifiers
  73. def __str__(self):
  74. return "<<<" + self.serialno + ", " + self.status + ", %s>>>" % self.qualifiers
  75. class WifiManager:
  76. '''
  77. Simulates Android WifiManager.
  78. @see: http://developer.android.com/reference/android/net/wifi/WifiManager.html
  79. '''
  80. WIFI_STATE_DISABLING = 0
  81. WIFI_STATE_DISABLED = 1
  82. WIFI_STATE_ENABLING = 2
  83. WIFI_STATE_ENABLED = 3
  84. WIFI_STATE_UNKNOWN = 4
  85. WIFI_IS_ENABLED_RE = re.compile('Wi-Fi is enabled')
  86. WIFI_IS_DISABLED_RE = re.compile('Wi-Fi is disabled')
  87. def __init__(self, device):
  88. self.device = device
  89. def getWifiState(self):
  90. '''
  91. Gets the Wi-Fi enabled state.
  92. @return: One of WIFI_STATE_DISABLED, WIFI_STATE_DISABLING, WIFI_STATE_ENABLED, WIFI_STATE_ENABLING, WIFI_STATE_UNKNOWN
  93. '''
  94. result = self.device.shell('dumpsys wifi')
  95. if result:
  96. state = result.splitlines()[0]
  97. if self.WIFI_IS_ENABLED_RE.match(state):
  98. return self.WIFI_STATE_ENABLED
  99. elif self.WIFI_IS_DISABLED_RE.match(state):
  100. return self.WIFI_STATE_DISABLED
  101. print >> sys.stderr, "UNKNOWN WIFI STATE:", state
  102. return self.WIFI_STATE_UNKNOWN
  103. class AdbClient:
  104. def __init__(self, serialno=None, hostname=HOSTNAME, port=PORT, settransport=True, reconnect=True, ignoreversioncheck=False):
  105. self.Log = AdbClient.__Log(self)
  106. self.serialno = serialno
  107. self.hostname = hostname
  108. self.port = port
  109. self.reconnect = reconnect
  110. self.__connect()
  111. self.checkVersion(ignoreversioncheck)
  112. self.build = {}
  113. ''' Build properties '''
  114. self.__displayInfo = None
  115. ''' Cached display info. Reset it to C{None} to force refetching display info '''
  116. self.display = {}
  117. ''' The map containing the device's physical display properties: width, height and density '''
  118. self.isTransportSet = False
  119. if settransport and serialno != None:
  120. self.__setTransport()
  121. self.build[VERSION_SDK_PROPERTY] = int(self.__getProp(VERSION_SDK_PROPERTY))
  122. self.initDisplayProperties()
  123. @staticmethod
  124. def setAlarm(timeout):
  125. osName = platform.system()
  126. if osName.startswith('Windows'): # alarm is not implemented in Windows
  127. return
  128. if DEBUG:
  129. print >> sys.stderr, "setAlarm(%d)" % timeout
  130. signal.alarm(timeout)
  131. def setSerialno(self, serialno):
  132. if self.isTransportSet:
  133. raise ValueError("Transport is already set, serialno cannot be set once this is done.")
  134. self.serialno = serialno
  135. self.__setTransport()
  136. self.build[VERSION_SDK_PROPERTY] = int(self.__getProp(VERSION_SDK_PROPERTY))
  137. def setReconnect(self, val):
  138. self.reconnect = val
  139. def __connect(self):
  140. if DEBUG:
  141. print >> sys.stderr, "__connect()"
  142. self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  143. self.socket.settimeout(TIMEOUT)
  144. try:
  145. self.socket.connect((self.hostname, self.port))
  146. except socket.error, ex:
  147. raise RuntimeError("ERROR: Connecting to %s:%d: %s.\nIs adb running on your computer?" % (self.socket, self.port, ex))
  148. def close(self):
  149. if DEBUG:
  150. print >> sys.stderr, "Closing socket...", self.socket
  151. if self.socket:
  152. self.socket.close()
  153. def __del__(self):
  154. try:
  155. self.close()
  156. except:
  157. pass
  158. def __send(self, msg, checkok=True, reconnect=False):
  159. if DEBUG:
  160. print >> sys.stderr, "__send(%s, checkok=%s, reconnect=%s)" % (msg, checkok, reconnect)
  161. if not re.search('^host:', msg):
  162. if not self.isTransportSet:
  163. self.__setTransport()
  164. else:
  165. self.checkConnected()
  166. b = bytearray(msg, 'utf-8')
  167. self.socket.send('%04X%s' % (len(b), b))
  168. if checkok:
  169. self.__checkOk()
  170. if reconnect:
  171. if DEBUG:
  172. print >> sys.stderr, " __send: reconnecting"
  173. self.__connect()
  174. self.__setTransport()
  175. def __receive(self, nob=None):
  176. if DEBUG:
  177. print >> sys.stderr, "__receive()"
  178. self.checkConnected()
  179. if nob is None:
  180. nob = int(self.socket.recv(4), 16)
  181. if DEBUG:
  182. print >> sys.stderr, " __receive: receiving", nob, "bytes"
  183. recv = bytearray()
  184. nr = 0
  185. while nr < nob:
  186. chunk = self.socket.recv(min((nob - nr), 4096))
  187. recv.extend(chunk)
  188. nr += len(chunk)
  189. if DEBUG:
  190. print >> sys.stderr, " __receive: returning len=", len(recv)
  191. return str(recv)
  192. def __checkOk(self):
  193. if DEBUG:
  194. print >> sys.stderr, "__checkOk()"
  195. self.checkConnected()
  196. self.setAlarm(TIMEOUT)
  197. recv = self.socket.recv(4)
  198. if DEBUG:
  199. print >> sys.stderr, " __checkOk: recv=", repr(recv)
  200. try:
  201. if recv != OKAY:
  202. error = self.socket.recv(1024)
  203. if error.startswith('0049'):
  204. raise RuntimeError("ERROR: This computer is unauthorized. Please check the confirmation dialog on your device.")
  205. else:
  206. raise RuntimeError("ERROR: %s %s" % (repr(recv), error))
  207. finally:
  208. self.setAlarm(0)
  209. if DEBUG:
  210. print >> sys.stderr, " __checkOk: returning True"
  211. return True
  212. def checkConnected(self):
  213. if DEBUG:
  214. print >> sys.stderr, "checkConnected()"
  215. if not self.socket:
  216. raise RuntimeError("ERROR: Not connected")
  217. if DEBUG:
  218. print >> sys.stderr, " checkConnected: returning True"
  219. return True
  220. def checkVersion(self, ignoreversioncheck=False, reconnect=True):
  221. if DEBUG:
  222. print >> sys.stderr, "checkVersion(reconnect=%s) ignoreversioncheck=%s" % (reconnect, ignoreversioncheck)
  223. self.__send('host:version', reconnect=False)
  224. # HACK: MSG_WAITALL not available on windows
  225. #version = self.socket.recv(8, socket.MSG_WAITALL)
  226. version = self.__readExactly(self.socket, 8)
  227. VALID_ADB_VERSIONS = ["00040020", "0004001f"]
  228. if not (version in VALID_ADB_VERSIONS) and not ignoreversioncheck:
  229. raise RuntimeError("ERROR: Incorrect ADB server version %s (expecting one of %s)" % (version, VALID_ADB_VERSIONS))
  230. if reconnect:
  231. self.__connect()
  232. def __setTransport(self):
  233. if DEBUG:
  234. print >> sys.stderr, "__setTransport()"
  235. if not self.serialno:
  236. raise ValueError("serialno not set, empty or None")
  237. self.checkConnected()
  238. serialnoRE = re.compile(self.serialno)
  239. found = False
  240. devices = self.getDevices()
  241. if len(devices) == 0:
  242. raise RuntimeError("ERROR: There are no connected devices")
  243. for device in devices:
  244. if serialnoRE.match(device.serialno):
  245. found = True
  246. break
  247. if not found:
  248. raise RuntimeError("ERROR: couldn't find device that matches '%s' in %s" % (self.serialno, devices))
  249. self.serialno = device.serialno
  250. msg = 'host:transport:%s' % self.serialno
  251. if DEBUG:
  252. print >> sys.stderr, " __setTransport: msg=", msg
  253. self.__send(msg, reconnect=False)
  254. self.isTransportSet = True
  255. def __checkTransport(self):
  256. if not self.isTransportSet:
  257. raise RuntimeError("ERROR: Transport is not set")
  258. def __readExactly(self, sock, size):
  259. if DEBUG:
  260. print >> sys.stderr, "__readExactly(socket=%s, size=%d)" % (socket, size)
  261. _buffer = ''
  262. while len(_buffer) < size:
  263. data = sock.recv(size-len(_buffer))
  264. if not data:
  265. break
  266. _buffer+=data
  267. return _buffer
  268. def getDevices(self):
  269. if DEBUG:
  270. print >> sys.stderr, "getDevices()"
  271. self.__send('host:devices-l', checkok=False)
  272. try:
  273. self.__checkOk()
  274. except RuntimeError, ex:
  275. print >> sys.stderr, "**ERROR:", ex
  276. return None
  277. devices = []
  278. for line in self.__receive().splitlines():
  279. devices.append(Device.factory(line))
  280. self.__connect()
  281. return devices
  282. def shell(self, cmd=None):
  283. if DEBUG:
  284. print >> sys.stderr, "shell(cmd=%s)" % cmd
  285. self.__checkTransport()
  286. if cmd:
  287. self.__send('shell:%s' % cmd, checkok=True, reconnect=False)
  288. out = ''
  289. while True:
  290. _str = None
  291. try:
  292. _str = self.socket.recv(4096)
  293. except Exception, ex:
  294. print >> sys.stderr, "ERROR:", ex
  295. if not _str:
  296. break
  297. out += _str
  298. if self.reconnect:
  299. if DEBUG:
  300. print >> sys.stderr, "Reconnecting..."
  301. self.close()
  302. self.__connect()
  303. self.__setTransport()
  304. return out
  305. else:
  306. self.__send('shell:')
  307. # sin = self.socket.makefile("rw")
  308. # sout = self.socket.makefile("r")
  309. # return (sin, sin)
  310. sout = adbClient.socket.makefile("r")
  311. return sout
  312. def __getRestrictedScreen(self):
  313. ''' Gets C{mRestrictedScreen} values from dumpsys. This is a method to obtain display dimensions '''
  314. rsRE = re.compile('\s*mRestrictedScreen=\((?P<x>\d+),(?P<y>\d+)\) (?P<w>\d+)x(?P<h>\d+)')
  315. for line in self.shell('dumpsys window').splitlines():
  316. m = rsRE.match(line)
  317. if m:
  318. return m.groups()
  319. raise RuntimeError("Couldn't find mRestrictedScreen in 'dumpsys window'")
  320. def getDisplayInfo(self):
  321. self.__checkTransport()
  322. displayInfo = self.getLogicalDisplayInfo()
  323. if displayInfo:
  324. return displayInfo
  325. displayInfo = self.getPhysicalDisplayInfo()
  326. if displayInfo:
  327. return displayInfo
  328. raise RuntimeError("Couldn't find display info in 'wm size', 'dumpsys display' or 'dumpsys window'")
  329. def getLogicalDisplayInfo(self):
  330. '''
  331. Gets C{mDefaultViewport} and then C{deviceWidth} and C{deviceHeight} values from dumpsys.
  332. This is a method to obtain display logical dimensions and density
  333. '''
  334. self.__checkTransport()
  335. logicalDisplayRE = re.compile('.*DisplayViewport{valid=true, .*orientation=(?P<orientation>\d+), .*deviceWidth=(?P<width>\d+), deviceHeight=(?P<height>\d+).*')
  336. for line in self.shell('dumpsys display').splitlines():
  337. m = logicalDisplayRE.search(line, 0)
  338. if m:
  339. self.__displayInfo = {}
  340. for prop in [ 'width', 'height', 'orientation' ]:
  341. self.__displayInfo[prop] = int(m.group(prop))
  342. for prop in [ 'density' ]:
  343. d = self.__getDisplayDensity(None, strip=True, invokeGetPhysicalDisplayIfNotFound=True)
  344. if d:
  345. self.__displayInfo[prop] = d
  346. else:
  347. # No available density information
  348. self.__displayInfo[prop] = -1.0
  349. return self.__displayInfo
  350. return None
  351. def getPhysicalDisplayInfo(self):
  352. ''' Gets C{mPhysicalDisplayInfo} values from dumpsys. This is a method to obtain display dimensions and density'''
  353. self.__checkTransport()
  354. phyDispRE = re.compile('Physical size: (?P<width>)x(?P<height>).*Physical density: (?P<density>)', re.MULTILINE)
  355. m = phyDispRE.search(self.shell('wm size; wm density'))
  356. if m:
  357. displayInfo = {}
  358. for prop in [ 'width', 'height' ]:
  359. displayInfo[prop] = int(m.group(prop))
  360. for prop in [ 'density' ]:
  361. displayInfo[prop] = float(m.group(prop))
  362. return displayInfo
  363. phyDispRE = re.compile('.*PhysicalDisplayInfo{(?P<width>\d+) x (?P<height>\d+), .*, density (?P<density>[\d.]+).*')
  364. for line in self.shell('dumpsys display').splitlines():
  365. m = phyDispRE.search(line, 0)
  366. if m:
  367. displayInfo = {}
  368. for prop in [ 'width', 'height' ]:
  369. displayInfo[prop] = int(m.group(prop))
  370. for prop in [ 'density' ]:
  371. # In mPhysicalDisplayInfo density is already a factor, no need to calculate
  372. displayInfo[prop] = float(m.group(prop))
  373. return displayInfo
  374. # This could also be mSystem or mOverscanScreen
  375. phyDispRE = re.compile('\s*mUnrestrictedScreen=\((?P<x>\d+),(?P<y>\d+)\) (?P<width>\d+)x(?P<height>\d+)')
  376. # This is known to work on older versions (i.e. API 10) where mrestrictedScreen is not available
  377. dispWHRE = re.compile('\s*DisplayWidth=(?P<width>\d+) *DisplayHeight=(?P<height>\d+)')
  378. for line in self.shell('dumpsys window').splitlines():
  379. m = phyDispRE.search(line, 0)
  380. if not m:
  381. m = dispWHRE.search(line, 0)
  382. if m:
  383. displayInfo = {}
  384. for prop in [ 'width', 'height' ]:
  385. displayInfo[prop] = int(m.group(prop))
  386. for prop in [ 'density' ]:
  387. d = self.__getDisplayDensity(None, strip=True, invokeGetPhysicalDisplayIfNotFound=False)
  388. if d:
  389. displayInfo[prop] = d
  390. else:
  391. # No available density information
  392. displayInfo[prop] = -1.0
  393. return displayInfo
  394. def __getProp(self, key, strip=True):
  395. if DEBUG:
  396. print >> sys.stderr, "__getProp(%s, %s)" % (key, strip)
  397. prop = self.shell('getprop %s' % key)
  398. if strip:
  399. prop = prop.rstrip('\r\n')
  400. if DEBUG:
  401. print >> sys.stderr, " __getProp: returning '%s'" % prop
  402. return prop
  403. def __getDisplayWidth(self, key, strip=True):
  404. if self.__displayInfo and 'width' in self.__displayInfo:
  405. return self.__displayInfo['width']
  406. return self.getDisplayInfo()['width']
  407. def __getDisplayHeight(self, key, strip=True):
  408. if self.__displayInfo and 'height' in self.__displayInfo:
  409. return self.__displayInfo['height']
  410. return self.getDisplayInfo()['height']
  411. def __getDisplayOrientation(self, key, strip=True):
  412. if self.__displayInfo and 'orientation' in self.__displayInfo:
  413. return self.__displayInfo['orientation']
  414. displayInfo = self.getDisplayInfo()
  415. if 'orientation' in displayInfo:
  416. return displayInfo['orientation']
  417. # Fallback method to obtain the orientation
  418. # See https://github.com/dtmilano/AndroidViewClient/issues/128
  419. surfaceOrientationRE = re.compile('SurfaceOrientation:\s+(\d+)')
  420. output = self.shell('dumpsys input')
  421. m = surfaceOrientationRE.search(output)
  422. if m:
  423. return int(m.group(1))
  424. # We couldn't obtain the orientation
  425. return -1
  426. def __getDisplayDensity(self, key, strip=True, invokeGetPhysicalDisplayIfNotFound=True):
  427. if self.__displayInfo and 'density' in self.__displayInfo: # and self.__displayInfo['density'] != -1: # FIXME: need more testing
  428. return self.__displayInfo['density']
  429. BASE_DPI = 160.0
  430. d = self.getProperty('ro.sf.lcd_density', strip)
  431. if d:
  432. return float(d)/BASE_DPI
  433. d = self.getProperty('qemu.sf.lcd_density', strip)
  434. if d:
  435. return float(d)/BASE_DPI
  436. if invokeGetPhysicalDisplayIfNotFound:
  437. return self.getPhysicalDisplayInfo()['density']
  438. return -1.0
  439. def getSystemProperty(self, key, strip=True):
  440. self.__checkTransport()
  441. return self.getProperty(key, strip)
  442. def getProperty(self, key, strip=True):
  443. ''' Gets the property value for key '''
  444. self.__checkTransport()
  445. import collections
  446. MAP_PROPS = collections.OrderedDict([
  447. (re.compile('display.width'), self.__getDisplayWidth),
  448. (re.compile('display.height'), self.__getDisplayHeight),
  449. (re.compile('display.density'), self.__getDisplayDensity),
  450. (re.compile('display.orientation'), self.__getDisplayOrientation),
  451. (re.compile('.*'), self.__getProp),
  452. ])
  453. '''Maps properties key values (as regexps) to instance methods to obtain its values.'''
  454. for kre in MAP_PROPS.keys():
  455. if kre.match(key):
  456. return MAP_PROPS[kre](key=key, strip=strip)
  457. raise ValueError("key='%s' does not match any map entry")
  458. def getSdkVersion(self):
  459. '''
  460. Gets the SDK version.
  461. '''
  462. self.__checkTransport()
  463. return self.build[VERSION_SDK_PROPERTY]
  464. def press(self, name, eventType=DOWN_AND_UP):
  465. self.__checkTransport()
  466. if isinstance(name, unicode):
  467. name = name.decode('ascii', errors='replace')
  468. cmd = 'input keyevent %s' % name
  469. if DEBUG:
  470. print >> sys.stderr, "press(%s)" % cmd
  471. self.shell(cmd)
  472. def longPress(self, name, duration=0.5, dev='/dev/input/event0'):
  473. self.__checkTransport()
  474. # WORKAROUND:
  475. # Using 'input keyevent --longpress POWER' does not work correctly in
  476. # KitKat (API 19), it sends a short instead of a long press.
  477. # This uses the events instead, but it may vary from device to device.
  478. # The events sent are device dependent and may not work on other devices.
  479. # If this does not work on your device please do:
  480. # $ adb shell getevent -l
  481. # and post the output to https://github.com/dtmilano/AndroidViewClient/issues
  482. # specifying the device and API level.
  483. if name[0:4] == 'KEY_':
  484. name = name[4:]
  485. if name in KEY_MAP:
  486. self.shell('sendevent %s 1 %d 1' % (dev, KEY_MAP[name]))
  487. self.shell('sendevent %s 0 0 0' % dev)
  488. time.sleep(duration)
  489. self.shell('sendevent %s 1 %d 0' % (dev, KEY_MAP[name]))
  490. self.shell('sendevent %s 0 0 0' % dev)
  491. return
  492. version = self.getSdkVersion()
  493. if version >= 19:
  494. cmd = 'input keyevent --longpress %s' % name
  495. if DEBUG:
  496. print >> sys.stderr, "longPress(%s)" % cmd
  497. self.shell(cmd)
  498. else:
  499. raise RuntimeError("longpress: not supported for API < 19 (version=%d)" % version)
  500. def startActivity(self, component=None, flags=None, uri=None):
  501. self.__checkTransport()
  502. cmd = 'am start'
  503. if component:
  504. cmd += ' -n %s' % component
  505. if flags:
  506. cmd += ' -f %s' % flags
  507. if uri:
  508. cmd += ' %s' % uri
  509. if DEBUG:
  510. print >> sys.stderr, "Starting activity: %s" % cmd
  511. out = self.shell(cmd)
  512. if re.search(r"(Error type)|(Error: )|(Cannot find 'App')", out, re.IGNORECASE | re.MULTILINE):
  513. raise RuntimeError(out)
  514. def takeSnapshot(self, reconnect=False):
  515. '''
  516. Takes a snapshot of the device and return it as a PIL Image.
  517. '''
  518. self.__checkTransport()
  519. try:
  520. from PIL import Image
  521. except:
  522. raise Exception("You have to install PIL to use takeSnapshot()")
  523. self.__send('framebuffer:', checkok=True, reconnect=False)
  524. import struct
  525. # case 1: // version
  526. # return 12; // bpp, size, width, height, 4*(length, offset)
  527. received = self.__receive(1 * 4 + 12 * 4)
  528. (version, bpp, size, width, height, roffset, rlen, boffset, blen, goffset, glen, aoffset, alen) = struct.unpack('<' + 'L' * 13, received)
  529. if DEBUG:
  530. print >> sys.stderr, " takeSnapshot:", (version, bpp, size, width, height, roffset, rlen, boffset, blen, goffset, glen, aoffset, alen)
  531. offsets = {roffset:'R', goffset:'G', boffset:'B'}
  532. if bpp == 32:
  533. if alen != 0:
  534. offsets[aoffset] = 'A'
  535. else:
  536. warnings.warn('''framebuffer is specified as 32bpp but alpha length is 0''')
  537. argMode = ''.join([offsets[o] for o in sorted(offsets)])
  538. if DEBUG:
  539. print >> sys.stderr, " takeSnapshot:", (version, bpp, size, width, height, roffset, rlen, boffset, blen, goffset, blen, aoffset, alen, argMode)
  540. if argMode == 'BGRA':
  541. argMode = 'RGBA'
  542. if bpp == 16:
  543. mode = 'RGB'
  544. argMode += ';16'
  545. else:
  546. mode = argMode
  547. self.__send('\0', checkok=False, reconnect=False)
  548. if DEBUG:
  549. print >> sys.stderr, " takeSnapshot: reading %d bytes" % (size)
  550. received = self.__receive(size)
  551. if reconnect:
  552. self.__connect()
  553. self.__setTransport()
  554. if DEBUG:
  555. print >> sys.stderr, " takeSnapshot: Image.frombuffer(%s, %s, %s, %s, %s, %s, %s)" % (mode, (width, height), 'data', 'raw', argMode, 0, 1)
  556. image = Image.frombuffer(mode, (width, height), received, 'raw', argMode, 0, 1)
  557. # Just in case let's get the real image size
  558. (w, h) = image.size
  559. if w == self.display['height'] and h == self.display['width']:
  560. # FIXME: We are not catching the 180 degrees rotation here
  561. if 'orientation' in self.display:
  562. r = (0, 90, 180, -90)[self.display['orientation']]
  563. else:
  564. r = 90
  565. image = image.rotate(r)
  566. return image
  567. def __transformPointByOrientation(self, (x, y), orientationOrig, orientationDest):
  568. if orientationOrig != orientationDest:
  569. if orientationDest == 1:
  570. _x = x
  571. x = self.display['width'] - y
  572. y = _x
  573. elif orientationDest == 3:
  574. _x = x
  575. x = y
  576. y = self.display['height'] - _x
  577. return (x, y)
  578. def touch(self, x, y, orientation=-1, eventType=DOWN_AND_UP):
  579. if DEBUG_TOUCH:
  580. print >> sys.stderr, "touch(x=", x, ", y=", y, ", orientation=", orientation, ", eventType=", eventType, ")"
  581. self.__checkTransport()
  582. if orientation == -1:
  583. orientation = self.display['orientation']
  584. self.shell('input tap %d %d' % self.__transformPointByOrientation((x, y), orientation, self.display['orientation']))
  585. def touchDip(self, x, y, orientation=-1, eventType=DOWN_AND_UP):
  586. if DEBUG_TOUCH:
  587. print >> sys.stderr, "touchDip(x=", x, ", y=", y, ", orientation=", orientation, ", eventType=", eventType, ")"
  588. self.__checkTransport()
  589. if orientation == -1:
  590. orientation = self.display['orientation']
  591. x = x * self.display['density']
  592. y = y * self.display['density']
  593. self.touch(x, y, orientation, eventType)
  594. def longTouch(self, x, y, duration=2000, orientation=-1):
  595. '''
  596. Long touches at (x, y)
  597. @param duration: duration in ms
  598. @param orientation: the orientation (-1: undefined)
  599. This workaround was suggested by U{HaMi<http://stackoverflow.com/users/2571957/hami>}
  600. '''
  601. self.__checkTransport()
  602. self.drag((x, y), (x, y), duration, orientation)
  603. def drag(self, (x0, y0), (x1, y1), duration, steps=1, orientation=-1):
  604. '''
  605. Sends drag event n PX (actually it's using C{input swipe} command.
  606. @param (x0, y0): starting point in PX
  607. @param (x1, y1): ending point in PX
  608. @param duration: duration of the event in ms
  609. @param steps: number of steps (currently ignored by @{input swipe})
  610. @param orientation: the orientation (-1: undefined)
  611. '''
  612. self.__checkTransport()
  613. if orientation == -1:
  614. orientation = self.display['orientation']
  615. (x0, y0) = self.__transformPointByOrientation((x0, y0), orientation, self.display['orientation'])
  616. (x1, y1) = self.__transformPointByOrientation((x1, y1), orientation, self.display['orientation'])
  617. version = self.getSdkVersion()
  618. if version <= 15:
  619. raise RuntimeError('drag: API <= 15 not supported (version=%d)' % version)
  620. elif version <= 17:
  621. self.shell('input swipe %d %d %d %d' % (x0, y0, x1, y1))
  622. else:
  623. self.shell('input touchscreen swipe %d %d %d %d %d' % (x0, y0, x1, y1, duration))
  624. def dragDip(self, (x0, y0), (x1, y1), duration, steps=1, orientation=-1):
  625. '''
  626. Sends drag event in DIP (actually it's using C{input swipe} command.
  627. @param (x0, y0): starting point in DIP
  628. @param (x1, y1): ending point in DIP
  629. @param duration: duration of the event in ms
  630. @param steps: number of steps (currently ignored by @{input swipe}
  631. '''
  632. self.__checkTransport()
  633. if orientation == -1:
  634. orientation = self.display['orientation']
  635. density = self.display['density'] if self.display['density'] > 0 else 1
  636. x0 = x0 * density
  637. y0 = y0 * density
  638. x1 = x1 * density
  639. y1 = y1 * density
  640. self.drag((x0, y0), (x1, y1), duration, steps, orientation)
  641. def type(self, text):
  642. self.__checkTransport()
  643. self.shell(u'input text "%s"' % text)
  644. def wake(self):
  645. self.__checkTransport()
  646. if not self.isScreenOn():
  647. self.shell('input keyevent POWER')
  648. def isLocked(self):
  649. '''
  650. Checks if the device screen is locked.
  651. @return True if the device screen is locked
  652. '''
  653. self.__checkTransport()
  654. lockScreenRE = re.compile('mShowingLockscreen=(true|false)')
  655. m = lockScreenRE.search(self.shell('dumpsys window policy'))
  656. if m:
  657. return (m.group(1) == 'true')
  658. raise RuntimeError("Couldn't determine screen lock state")
  659. def isScreenOn(self):
  660. '''
  661. Checks if the screen is ON.
  662. @return True if the device screen is ON
  663. '''
  664. self.__checkTransport()
  665. screenOnRE = re.compile('mScreenOnFully=(true|false)')
  666. m = screenOnRE.search(self.shell('dumpsys window policy'))
  667. if m:
  668. return (m.group(1) == 'true')
  669. raise RuntimeError("Couldn't determine screen ON state")
  670. def unlock(self):
  671. '''
  672. Unlocks the screen of the device.
  673. '''
  674. self.__checkTransport()
  675. self.shell('input keyevent MENU')
  676. self.shell('input keyevent BACK')
  677. @staticmethod
  678. def percentSame(image1, image2):
  679. '''
  680. Returns the percent of pixels that are equal
  681. @author: catshoes
  682. '''
  683. # If the images differ in size, return 0% same.
  684. size_x1, size_y1 = image1.size
  685. size_x2, size_y2 = image2.size
  686. if (size_x1 != size_x2 or
  687. size_y1 != size_y2):
  688. return 0
  689. # Images are the same size
  690. # Return the percent of pixels that are equal.
  691. numPixelsSame = 0
  692. numPixelsTotal = size_x1 * size_y1
  693. image1Pixels = image1.load()
  694. image2Pixels = image2.load()
  695. # Loop over all pixels, comparing pixel in image1 to image2
  696. for x in range(size_x1):
  697. for y in range(size_y1):
  698. if (image1Pixels[x, y] == image2Pixels[x, y]):
  699. numPixelsSame += 1
  700. return numPixelsSame / float(numPixelsTotal)
  701. @staticmethod
  702. def sameAs(image1, image2, percent=1.0):
  703. '''
  704. Compares 2 images
  705. @author: catshoes
  706. '''
  707. return (AdbClient.percentSame(image1, image2) >= percent)
  708. def isKeyboardShown(self):
  709. '''
  710. Whether the keyboard is displayed.
  711. '''
  712. self.__checkTransport()
  713. dim = self.shell('dumpsys input_method')
  714. if dim:
  715. # FIXME: API >= 15 ?
  716. return "mInputShown=true" in dim
  717. return False
  718. def initDisplayProperties(self):
  719. self.__checkTransport()
  720. self.__displayInfo = None
  721. self.display['width'] = self.getProperty('display.width')
  722. self.display['height'] = self.getProperty('display.height')
  723. self.display['density'] = self.getProperty('display.density')
  724. self.display['orientation'] = self.getProperty('display.orientation')
  725. def log(self, tag, message, priority='D', verbose=False):
  726. if DEBUG_LOG:
  727. print >> sys.stderr, "log(tag=%s, message=%s, priority=%s, verbose=%s)" % (tag, message, priority, verbose)
  728. self.__checkTransport()
  729. message = self.substituteDeviceTemplate(message)
  730. if verbose or priority == 'V':
  731. print >> sys.stderr, tag+':', message
  732. self.shell('log -p %c -t "%s" %s' % (priority, tag, message))
  733. class __Log():
  734. '''
  735. Log class to simulate C{android.util.Log}
  736. '''
  737. def __init__(self, adbClient):
  738. self.adbClient = adbClient
  739. def __getattr__(self, attr):
  740. '''
  741. Returns the corresponding log method or @C{AttributeError}.
  742. '''
  743. if attr in ['v', 'd', 'i', 'w', 'e']:
  744. return lambda tag, message, verbose: self.adbClient.log(tag, message, priority=attr.upper(), verbose=verbose)
  745. raise AttributeError(self.__class__.__name__ + ' has no attribute "%s"' % attr)
  746. def getSystemService(self, name):
  747. if name == WIFI_SERVICE:
  748. return WifiManager(self)
  749. def getWindows(self):
  750. self.__checkTransport()
  751. windows = {}
  752. dww = self.shell('dumpsys window windows')
  753. if DEBUG_WINDOWS: print >> sys.stderr, dww
  754. lines = dww.splitlines()
  755. widRE = re.compile('^ *Window #%s Window{%s (u\d+ )?%s?.*}:' %
  756. (_nd('num'), _nh('winId'), _ns('activity', greedy=True)))
  757. currentFocusRE = re.compile('^ mCurrentFocus=Window{%s .*' % _nh('winId'))
  758. viewVisibilityRE = re.compile(' mViewVisibility=0x%s ' % _nh('visibility'))
  759. # This is for 4.0.4 API-15
  760. containingFrameRE = re.compile('^ *mContainingFrame=\[%s,%s\]\[%s,%s\] mParentFrame=\[%s,%s\]\[%s,%s\]' %
  761. (_nd('cx'), _nd('cy'), _nd('cw'), _nd('ch'), _nd('px'), _nd('py'), _nd('pw'), _nd('ph')))
  762. contentFrameRE = re.compile('^ *mContentFrame=\[%s,%s\]\[%s,%s\] mVisibleFrame=\[%s,%s\]\[%s,%s\]' %
  763. (_nd('x'), _nd('y'), _nd('w'), _nd('h'), _nd('vx'), _nd('vy'), _nd('vx1'), _nd('vy1')))
  764. # This is for 4.1 API-16
  765. framesRE = re.compile('^ *Frames: containing=\[%s,%s\]\[%s,%s\] parent=\[%s,%s\]\[%s,%s\]' %
  766. (_nd('cx'), _nd('cy'), _nd('cw'), _nd('ch'), _nd('px'), _nd('py'), _nd('pw'), _nd('ph')))
  767. contentRE = re.compile('^ *content=\[%s,%s\]\[%s,%s\] visible=\[%s,%s\]\[%s,%s\]' %
  768. (_nd('x'), _nd('y'), _nd('w'), _nd('h'), _nd('vx'), _nd('vy'), _nd('vx1'), _nd('vy1')))
  769. policyVisibilityRE = re.compile('mPolicyVisibility=%s ' % _ns('policyVisibility', greedy=True))
  770. for l in range(len(lines)):
  771. m = widRE.search(lines[l])
  772. if m:
  773. num = int(m.group('num'))
  774. winId = m.group('winId')
  775. activity = m.group('activity')
  776. wvx = 0
  777. wvy = 0
  778. wvw = 0
  779. wvh = 0
  780. px = 0
  781. py = 0
  782. visibility = -1
  783. policyVisibility = 0x0
  784. for l2 in range(l+1, len(lines)):
  785. m = widRE.search(lines[l2])
  786. if m:
  787. l += (l2-1)
  788. break
  789. m = viewVisibilityRE.search(lines[l2])
  790. if m:
  791. visibility = int(m.group('visibility'))
  792. if DEBUG_COORDS: print >> sys.stderr, "getWindows: visibility=", visibility
  793. if self.build[VERSION_SDK_PROPERTY] >= 17:
  794. wvx, wvy = (0, 0)
  795. wvw, wvh = (0, 0)
  796. if self.build[VERSION_SDK_PROPERTY] >= 16:
  797. m = framesRE.search(lines[l2])
  798. if m:
  799. px, py = obtainPxPy(m)
  800. m = contentRE.search(lines[l2+1])
  801. if m:
  802. # FIXME: the information provided by 'dumpsys window windows' in 4.2.1 (API 16)
  803. # when there's a system dialog may not be correct and causes the View coordinates
  804. # be offset by this amount, see
  805. # https://github.com/dtmilano/AndroidViewClient/issues/29
  806. wvx, wvy = obtainVxVy(m)
  807. wvw, wvh = obtainVwVh(m)
  808. elif self.build[VERSION_SDK_PROPERTY] == 15:
  809. m = containingFrameRE.search(lines[l2])
  810. if m:
  811. px, py = obtainPxPy(m)
  812. m = contentFrameRE.search(lines[l2+1])
  813. if m:
  814. wvx, wvy = obtainVxVy(m)
  815. wvw, wvh = obtainVwVh(m)
  816. elif self.build[VERSION_SDK_PROPERTY] == 10:
  817. m = containingFrameRE.search(lines[l2])
  818. if m:
  819. px, py = obtainPxPy(m)
  820. m = contentFrameRE.search(lines[l2+1])
  821. if m:
  822. wvx, wvy = obtainVxVy(m)
  823. wvw, wvh = obtainVwVh(m)
  824. else:
  825. warnings.warn("Unsupported Android version %d" % self.build[VERSION_SDK_PROPERTY])
  826. #print >> sys.stderr, "Searching policyVisibility in", lines[l2]
  827. m = policyVisibilityRE.search(lines[l2])
  828. if m:
  829. policyVisibility = 0x0 if m.group('policyVisibility') == 'true' else 0x8
  830. windows[winId] = Window(num, winId, activity, wvx, wvy, wvw, wvh, px, py, visibility + policyVisibility)
  831. else:
  832. m = currentFocusRE.search(lines[l])
  833. if m:
  834. currentFocus = m.group('winId')
  835. if currentFocus in windows and windows[currentFocus].visibility == 0:
  836. if DEBUG_COORDS:
  837. print >> sys.stderr, "getWindows: focus=", currentFocus
  838. print >> sys.stderr, "getWindows:", windows[currentFocus]
  839. windows[currentFocus].focused = True
  840. return windows
  841. def getFocusedWindow(self):
  842. '''
  843. Gets the focused window.
  844. @return: The focused L{Window}.
  845. '''
  846. for window in self.getWindows().values():
  847. if window.focused:
  848. return window
  849. return None
  850. def getFocusedWindowName(self):
  851. '''
  852. Gets the focused window name.
  853. This is much like monkeyRunner's C{HierarchyView.getWindowName()}
  854. @return: The focused window name
  855. '''
  856. window = self.getFocusedWindow()
  857. if window:
  858. return window.activity
  859. return None
  860. def substituteDeviceTemplate(self, template):
  861. serialno = self.serialno.replace('.', '_').replace(':', '-')
  862. focusedWindowName = self.getFocusedWindowName().replace('/', '-').replace('.', '_')
  863. timestamp = datetime.datetime.now().isoformat()
  864. osName = platform.system()
  865. if osName.startswith('Windows'): # ':' not supported in filenames
  866. timestamp.replace(':', '_')
  867. _map = {
  868. 'serialno': serialno,
  869. 'focusedwindowname': focusedWindowName,
  870. 'timestamp': timestamp
  871. }
  872. return string.Template(template).substitute(_map)
  873. if __name__ == '__main__':
  874. adbClient = AdbClient(os.environ['ANDROID_SERIAL'])
  875. INTERACTIVE = False
  876. if INTERACTIVE:
  877. sout = adbClient.shell()
  878. prompt = re.compile(".+@android:(.*) [$#] \r\r\n")
  879. while True:
  880. try:
  881. cmd = raw_input('adb $ ')
  882. except EOFError:
  883. break
  884. if cmd == 'exit':
  885. break
  886. adbClient.socket.__send(cmd + "\r\n")
  887. sout.readline(4096) # eat first line, which is the command
  888. while True:
  889. line = sout.readline(4096)
  890. if prompt.match(line):
  891. break
  892. print line,
  893. if not line:
  894. break
  895. print "\nBye"
  896. else:
  897. print 'date:', adbClient.shell('date')