Skip to content

Commit 75cf5ac

Browse files
committed
Auto-generation of ids for NO_ID's View
- Added 'uniqueId' attribute to hold the generated ids - Fixed problem of finding adb.exe on Windows - All debug messages redirected to stderr - Added SDK version to temperature-converter-get-conversion.py example
1 parent 0f69ef7 commit 75cf5ac

2 files changed

Lines changed: 82 additions & 47 deletions

File tree

AndroidViewClient/examples/temperature-converter-get-conversion.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
This example starts the TemperatureConverter activity then type '123' into the 'Celsius' field.
77
Then a ViewClient is created to obtain the view dump and the current values of the views with
88
id/celsius and id/fahrenheith are obtained and the conversion printed to stdout.
9+
Finally, the fields are obtained by using their tags and again, conversion printed to stdout.
910
1011
@author: diego
1112
'''
@@ -45,39 +46,36 @@
4546

4647
# obtain the views by id
4748
celsius = vc.findViewById("id/celsius")
49+
if not celsius:
50+
raise "Couldn't find View with id/celsius"
4851
fahrenheit = vc.findViewById("id/fahrenheit")
52+
if not fahrenheit:
53+
raise "Couldn't find View with id/fahrenheit"
4954

5055

5156
# in android-15 this is text:mText while in previous versions it was just mText
52-
try:
57+
version = int(device.getSystemProperty('ro.build.version.sdk'))
58+
59+
if version >= 15:
5360
c = float(celsius.text_mText())
5461
f = float(fahrenheit.text_mText())
55-
56-
print "%.2f C => %.2f F" % (c, f)
57-
except:
58-
try:
59-
c = float(celsius.mText())
60-
f = float(fahrenheit.mText())
61-
62-
print "%.2f C => %.2f F" % (c, f)
63-
except:
64-
print "Unexpected error:", sys.exc_info()[0]
62+
else:
63+
c = float(celsius.mText())
64+
f = float(fahrenheit.mText())
65+
print "by id: %.2f C => %.2f F" % (c, f)
6566

6667
# obtain the views by tag
6768
celsius = vc.findViewByTag("celsius")
69+
if not celsius:
70+
raise "Couldn't find View with tag celsius"
6871
fahrenheit = vc.findViewByTag("fahrenheit")
72+
if not fahrenheit:
73+
raise "Couldn't find View with tag fahrenheit"
6974

70-
# in android-15 this is text:mText while in previous versions it was just mText
71-
try:
75+
if version >= 15:
7276
c = float(celsius.text_mText())
7377
f = float(fahrenheit.text_mText())
74-
75-
print "%.2f C => %.2f F" % (c, f)
76-
except:
77-
try:
78-
c = float(celsius.mText())
79-
f = float(fahrenheit.mText())
80-
81-
print "%.2f C => %.2f F" % (c, f)
82-
except:
83-
print "Unexpected error:", sys.exc_info()[0]
78+
else:
79+
c = float(celsius.mText())
80+
f = float(fahrenheit.mText())
81+
print "by tag: %.2f C => %.2f F" % (c, f)

AndroidViewClient/src/com/dtmilano/android/viewclient.py

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import re
1111
import socket
1212
import os
13+
import java
1314
from com.android.monkeyrunner import MonkeyDevice
1415

1516
DEBUG = False
@@ -21,13 +22,23 @@
2122
VIEW_SERVER_HOST = 'localhost'
2223
VIEW_SERVER_PORT = 4939
2324

25+
# this is probably the only reliable way of determining the OS in monkeyrunner
26+
os_name = java.lang.System.getProperty('os.name')
27+
if os_name.startswith('Windows'):
28+
ADB = 'adb.exe'
29+
else:
30+
ADB = 'adb'
31+
2432
OFFSET = 50
2533

2634
class View:
2735
'''
2836
View class
2937
'''
30-
38+
39+
GET_VISIBILITY_PROPERTY = 'getVisibility()'
40+
LAYOUT_TOP_MARGIN_PROPERTY = 'layout:layout_topMargin'
41+
3142
def __init__(self, map, device):
3243
'''
3344
Constructor
@@ -86,27 +97,32 @@ def innerMethod():
8697

8798
def __call__(self, *args, **kwargs):
8899
if DEBUG:
89-
print "__call__(%s)" % (args if args else None)
100+
print >>sys.stderr, "__call__(%s)" % (args if args else None)
90101

91102
def getX(self):
92103
x = 0
93-
if self.map['getVisibility()'] == 'VISIBLE':
104+
if self.GET_VISIBILITY_PROPERTY in self.map and self.map[self.GET_VISIBILITY_PROPERTY] == 'VISIBLE':
94105
x += int(self.map['layout:mLeft'])
95-
x += OFFSET/2
106+
#x += OFFSET/2
96107
return x
97108

98109
def getY(self):
99110
y = 0
100-
if self.map['getVisibility()'] == 'VISIBLE':
111+
if self.GET_VISIBILITY_PROPERTY in self.map and self.map[self.GET_VISIBILITY_PROPERTY] == 'VISIBLE':
101112
y += int(self.map['layout:mTop'])
113+
114+
#if self.LAYOUT_TOP_MARGIN_PROPERTY in self.map:
115+
# if DEBUG:
116+
# print >>sys.stderr, " adding top margin=%d" % int(self.map[self.LAYOUT_TOP_MARGIN_PROPERTY])
117+
# y += int(self.map[self.LAYOUT_TOP_MARGIN_PROPERTY])
102118
return y
103119

104120
def getXY(self):
105121
'''
106122
Returns the coordinates of this View
107123
'''
108124

109-
# FIXME: this usually don't return the real coordinates of the View but the coordinates
125+
# FIXME: this usually doesn't return the real coordinates of the View but the coordinates
110126
# relative to its parent, so to obtain the real coordinates the View root should
111127
# have to be traversed to the root adding the coordinates for every child
112128
x = self.getX()
@@ -125,15 +141,24 @@ def getXY(self):
125141
parent = parent.parent
126142
return (x, y+hy)
127143

144+
def getCoords(self):
145+
'''
146+
Gets the coords of the View
147+
'''
148+
(x, y) = self.getXY();
149+
w = int(self.map['layout:getWidth()'])
150+
h = int(self.map['layout:getHeight()'])
151+
return ((x, y), (x+w, y+h))
152+
128153
def touch(self, type=MonkeyDevice.DOWN_AND_UP):
129154
'''
130155
Touches this View
131156
'''
132157

133158
(x, y) = self.getXY()
134159
if DEBUG:
135-
print >>sys.stderr, "should click @ (%d, %d)" % (x, y)
136-
self.device.touch(x, y, type)
160+
print >>sys.stderr, "should touch @ (%d, %d)" % (x+OFFSET/2, y+OFFSET/2)
161+
self.device.touch(x+OFFSET/2, y+OFFSET/2, type)
137162

138163
def allPossibleNamesWithColon(self, name):
139164
l = []
@@ -183,7 +208,7 @@ class ViewClient:
183208
mapping is created.
184209
'''
185210

186-
def __init__(self, device, adb=os.path.join(ANDROID_HOME, 'platform-tools', 'adb')):
211+
def __init__(self, device, adb=os.path.join(ANDROID_HOME, 'platform-tools', ADB)):
187212
'''
188213
Constructor
189214
'''
@@ -223,15 +248,19 @@ def serviceResponse(self, response):
223248
def setViews(self, received):
224249
self.views = received.split("\n")
225250
if DEBUG:
226-
print "there are %d views in this dump" % len(self.views)
227-
251+
print >>sys.stderr, "there are %d views in this dump" % len(self.views)
252+
228253
def __splitAttrs(self, str, addViewToViewsById=False):
229254
'''
230255
Splits the view attributes in str and optionally adds the view id to the viewsById list.
231256
Returns the attributes map.
232257
'''
233258

234259
idRE = re.compile("(?P<viewId>id/\S+)")
260+
# FIXME: this split is incorrect if for example a text:mText contains spaces
261+
# maybe something like this should be used and the count the chars specified
262+
# and cut it
263+
#textRE = re.compile("(?P<attr>\S+)(\(\))?=\d+,(?P<text>.+)")
235264
attrRE = re.compile("(?P<attr>\S+)(\(\))?=\d+,(?P<val>\S+)")
236265
hashRE = re.compile("(?P<class>\S+)@(?P<oid>[0-9a-f]+)")
237266

@@ -241,8 +270,9 @@ def __splitAttrs(self, str, addViewToViewsById=False):
241270
if m:
242271
viewId = m.group('viewId')
243272
if DEBUG:
244-
print "found %s" % viewId
245-
273+
print >>sys.stderr, "found %s" % viewId
274+
275+
# FIXME: this split is incorrect if for example a text:mText contains spaces
246276
for attr in str.split():
247277
m = attrRE.match(attr)
248278
if m:
@@ -254,9 +284,14 @@ def __splitAttrs(self, str, addViewToViewsById=False):
254284
attrs['oid'] = m.group('oid')
255285
else:
256286
if DEBUG:
257-
print attr, "doesn't match"
287+
print >>sys.stderr, attr, "doesn't match"
258288

259289
if addViewToViewsById:
290+
if not viewId:
291+
# If the view has NO_ID we are assigning a default id here (id/no_id) which is
292+
# immediatelly incremented if another view with no id was found before to generate
293+
# a unique id
294+
viewId = "id/no_id"
260295
if viewId in self.viewsById:
261296
# sometimes the view ids are not unique, so let's generate a unique id here
262297
i = 1
@@ -267,9 +302,11 @@ def __splitAttrs(self, str, addViewToViewsById=False):
267302
i += 1
268303
viewId = newId
269304
if DEBUG:
270-
print "adding viewById %s" % viewId
271-
if viewId:
272-
self.viewsById[viewId] = attrs
305+
print >>sys.stderr, "adding viewById %s" % viewId
306+
# We are assigning a new attribute to keep the original id preserved, which could have
307+
# been NO_ID repeated multiple times
308+
attrs['uniqueId'] = viewId
309+
self.viewsById[viewId] = attrs
273310

274311
return attrs
275312

@@ -301,7 +338,7 @@ def traverse(self, root, indent=""):
301338
if not root:
302339
return
303340

304-
print "%s%s" % (indent, root)
341+
print >>sys.stderr, "%s%s" % (indent, root)
305342

306343
for ch in root.children:
307344
self.traverse(ch, indent=indent+" ")
@@ -323,9 +360,9 @@ def dump(self, windowId=-1):
323360

324361
s.close()
325362
if DEBUG_RECEIVED:
326-
print
327-
print received
328-
print
363+
print >>sys.stderr
364+
print >>sys.stderr, received
365+
print >>sys.stderr
329366
self.setViews(received)
330367
self.parseTree(self.views)
331368

@@ -348,10 +385,10 @@ def findViewByTag(self, tag):
348385
return self.findViewWithAttribute('getTag()', tag)
349386

350387
def findViewWithAttributeInTree(self, attr, val, root):
351-
if DEBUG: print "findViewWitAttributeInTree: checking if root=%s has attr=%s == %s" % (root.__smallStr__(), attr, val)
388+
if DEBUG: print >>sys.stderr, "findViewWitAttributeInTree: checking if root=%s has attr=%s == %s" % (root.__smallStr__(), attr, val)
352389

353390
if root and attr in root.map and root.map[attr] == val:
354-
if DEBUG: print "findViewWitAttributeInTree: FOUND: %s" % root.__smallStr__()
391+
if DEBUG: print >>sys.stderr, "findViewWitAttributeInTree: FOUND: %s" % root.__smallStr__()
355392
return root
356393
else:
357394
for ch in root.children:
@@ -381,4 +418,4 @@ def getViewIds(self):
381418
except:
382419
print "Don't expect this to do anything"
383420

384-
421+

0 commit comments

Comments
 (0)