Skip to content

Commit 51ba257

Browse files
committed
Added View.isClickable() support
- Added filter in ViewClient.findViewsContainingPoint() - Added tests for API 15 and API 17
1 parent 0a2c95f commit 51ba257

3 files changed

Lines changed: 72 additions & 17 deletions

File tree

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

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
DEBUG_DEVICE = DEBUG and False
3838
DEBUG_RECEIVED = DEBUG and False
3939
DEBUG_TREE = DEBUG and False
40-
DEBUG_GETATTR = DEBUG and False
40+
DEBUG_GETATTR = DEBUG and True
41+
DEBUG_CALL = DEBUG or True
4142
DEBUG_COORDS = DEBUG and True
4243
DEBUG_TOUCH = DEBUG and False
4344
DEBUG_STATUSBAR = DEBUG and False
@@ -313,8 +314,9 @@ def __getitem__(self, key):
313314

314315
def __getattr__(self, name):
315316
if DEBUG_GETATTR:
316-
print >>sys.stderr, "__getattr__(%s)" % (name)
317-
317+
print >>sys.stderr, "__getattr__(%s) version: %d" % (name, self.build[VERSION_SDK_PROPERTY])
318+
319+
# NOTE:
318320
# I should try to see if 'name' is a defined method
319321
# but it seems that if I call locals() here an infinite loop is entered
320322

@@ -326,22 +328,33 @@ def __getattr__(self, name):
326328
elif name.count("_") > 0:
327329
mangledList = self.allPossibleNamesWithColon(name)
328330
mangledName = self.intersection(mangledList, self.map.keys())
329-
if len(mangledName) > 0:
331+
if len(mangledName) > 0 and self.map.has_key(mangledName[0]):
330332
r = self.map[mangledName[0]]
331333
else:
332334
# Default behavior
333335
raise AttributeError, name
334336
else:
335-
# Default behavior
336-
raise AttributeError, name
337+
# try removing 'is' prefix
338+
if DEBUG_GETATTR:
339+
print >> sys.stderr, " __getattr__: trying without 'is' prefix"
340+
suffix = name[2:].lower()
341+
if self.map.has_key(suffix):
342+
r = self.map[suffix]
343+
else:
344+
# Default behavior
345+
raise AttributeError, name
337346

338347
# if the method name starts with 'is' let's assume its return value is boolean
339-
if name[:2] == 'is':
340-
r = True if r == 'true' else False
348+
# if name[:2] == 'is':
349+
# r = True if r == 'true' else False
350+
if r == 'true':
351+
r = True
352+
elif r == 'false':
353+
r = False
341354

342355
# this should not cached in some way
343356
def innerMethod():
344-
if DEBUG:
357+
if DEBUG_GETATTR:
345358
print >>sys.stderr, "innerMethod: %s returning %s" % (innerMethod.__name__, r)
346359
return r
347360

@@ -355,7 +368,7 @@ def innerMethod():
355368
return innerMethod
356369

357370
def __call__(self, *args, **kwargs):
358-
if DEBUG:
371+
if DEBUG_CALL:
359372
print >>sys.stderr, "__call__(%s)" % (args if args else None)
360373

361374
def getClass(self):
@@ -790,7 +803,10 @@ def add(self, child):
790803
'''
791804
child.parent = self
792805
self.children.append(child)
793-
806+
807+
def isClickable(self):
808+
return self.__getattr__('isClickable')()
809+
794810
def __smallStr__(self):
795811
__str = "View["
796812
if "class" in self.map:
@@ -1999,12 +2015,15 @@ def findViewWithContentDescriptionOrRaise(self, contentdescription, root="ROOT")
19992015

20002016
return self.__findViewWithAttributeInTreeOrRaise('content-desc', contentdescription, root)
20012017

2002-
def findViewsContainingPoint(self, (x, y)):
2018+
def findViewsContainingPoint(self, (x, y), filter=None):
20032019
'''
20042020
Finds the list of Views that contain the point (x, y).
20052021
'''
20062022

2007-
return [v for v in self.views if v.containsPoint((x,y))]
2023+
if not filter:
2024+
filter = lambda v: True
2025+
2026+
return [v for v in self.views if (v.containsPoint((x,y)) and filter(v))]
20082027

20092028
def getViewIds(self):
20102029
'''

AndroidViewClient/tests/com/dtmilano/android/mocks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127

128128
VIEW_MAP_API_8 = {'padding:mUserPaddingRight': '12', 'drawing:getSolidColor()': '0', 'getFilterTouchesWhenObscured()': 'false', 'drawing:isOpaque()': 'false', 'mPrivateFlags_DRAWING_CACHE_INVALID': '0x0', 'focus:isFocusable()': 'true', 'mSystemUiVisibility': '0', 'isSoundEffectsEnabled()': 'true', 'layout:layout_width': 'MATCH_PARENT', 'layout:getWidth()': '1140', 'drawing:isDrawingCacheEnabled()': 'false', 'mPrivateFlags_DRAWN': '0x20', 'text:getSelectionEnd()': '-1', 'getTag()': 'null', 'getEllipsize()': 'null', 'focus:hasFocus()': 'false', 'layout:getResolvedLayoutDirection()': 'RESOLVED_DIRECTION_LTR', 'measurement:mMinWidth': '64', 'padding:mUserPaddingEnd': '-1', 'isFocusableInTouchMode()': 'false', 'text:mTextDirection': 'INHERIT', 'isHovered()': 'false', 'layout:layout_leftMargin': '50', 'layout:layout_endMargin': '-2147483648', 'padding:mPaddingBottom': '4', 'measurement:mMeasuredHeight': '48', 'layout:getLayoutDirection()': 'INHERIT', 'layout:mBottom': '364', 'mSystemUiVisibility_SYSTEM_UI_FLAG_VISIBLE': '0x0', 'layout:layout_startMargin': '-2147483648', 'class': 'android.widget.ToggleButton', 'text:mText': 'Button with ID', 'padding:mPaddingRight': '12', 'mPrivateFlags': '-2130704336', 'layout:layout_bottomMargin': '10', 'layout:layout_height': 'WRAP_CONTENT', 'uniqueId': 'id/button_with_id', 'focus:isFocused()': 'false', 'measurement:mMeasuredWidth': '1140', 'padding:mUserPaddingRelative': 'false', 'text:getSelectionStart()': '-1', 'mViewFlags': '-1744814079', 'isClickable()': 'true', 'getScrollBarStyle()': 'INSIDE_OVERLAY', 'layout:layout_rightMargin': '50', 'padding:mUserPaddingLeft': '12', 'oid': 'b4781818', 'layout:getBaseline()': '29', 'isEnabled()': 'true', 'isChecked()': 'false', 'drawing:mLayerType': 'NONE', 'drawing:willNotDraw()': 'false', 'layout:mRight': '1190', 'drawing:willNotCacheDrawing()': 'false', 'mTop': '316', 'isHapticFeedbackEnabled()': 'true', 'getVisibility()': 'VISIBLE', 'scrolling:mScrollX': '0', 'text:mResolvedTextDirection': 'FIRST_STRONG', 'isInTouchMode()': 'true', 'padding:mPaddingTop': '4', 'layout:layout_weight': '0.0', 'measurement:mMinHeight': '48', 'mID': 'id/button_with_id', 'layout:layout_topMargin': '50', 'padding:mUserPaddingStart': '-1', 'padding:mPaddingLeft': '12', 'isSelected()': 'false', 'isActivated()': 'false', 'padding:mUserPaddingBottom': '4', 'layout:layout_gravity': 'NONE', 'mLeft': '50', 'layout:isLayoutRtl()': 'false', 'layout:getHeight()': '48', 'scrolling:mScrollY': '0'}
129129

130+
VIEW_MAP_API_17 = {u'clickable': u'true', u'bounds': ((323, 725), (475, 881)), u'enabled': u'true', 'uniqueId': 'id/no_id/33', u'text': u'6', u'selected': u'false', u'scrollable': u'false', u'focused': u'false', u'long-clickable': u'false', u'class': u'android.widget.Button', u'focusable': u'true', u'content-desc': u'', u'package': u'com.android.calculator2', u'checked': u'false', u'password': u'false', u'checkable': u'false', u'index': u'2'}
131+
130132
DUMPSYS_WINDOW_WINDOWS = """
131133
mock data
132134
mock data

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

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
from com.dtmilano.android.viewclient import *
3030
from mocks import MockDevice, MockViewServer
31-
from mocks import DUMP, DUMP_SAMPLE_UI, VIEW_MAP, VIEW_MAP_API_8, RUNNING, STOPPED, WINDOWS
31+
from mocks import DUMP, DUMP_SAMPLE_UI, VIEW_MAP, VIEW_MAP_API_8, VIEW_MAP_API_17, RUNNING, STOPPED, WINDOWS
3232

3333
# this is probably the only reliable way of determining the OS in monkeyrunner
3434
os_name = java.lang.System.getProperty('os.name')
@@ -249,14 +249,31 @@ def testViewTreeParent(self):
249249
for ch in root.children:
250250
self.assertTrue(ch.parent == root)
251251

252-
def testContainsPoint(self):
252+
def testContainsPoint_api15(self):
253253
v = View(VIEW_MAP, MockDevice(), 15)
254254
(X, Y, W, H) = v.getPositionAndSize()
255255
self.assertEqual(X, v.getX())
256256
self.assertEqual(Y, v.getY())
257257
self.assertEqual(W, v.getWidth())
258258
self.assertEqual(H, v.getHeight())
259259
self.assertTrue(v.containsPoint((v.getCenter())))
260+
261+
def testContainsPoint_api17(self):
262+
v = View(VIEW_MAP_API_17, MockDevice(), 17)
263+
(X, Y, W, H) = v.getPositionAndSize()
264+
self.assertEqual(X, v.getX())
265+
self.assertEqual(Y, v.getY())
266+
self.assertEqual(W, v.getWidth())
267+
self.assertEqual(H, v.getHeight())
268+
self.assertTrue(v.containsPoint((v.getCenter())))
269+
270+
def testIsClickable_api15(self):
271+
v = View(VIEW_MAP, MockDevice(), 15)
272+
self.assertTrue(v.isClickable())
273+
274+
def testIsClickable_api17(self):
275+
v = View(VIEW_MAP_API_17, MockDevice(), 17)
276+
self.assertTrue(v.isClickable())
260277

261278
class ViewClientTest(unittest.TestCase):
262279

@@ -856,7 +873,7 @@ def testUiViewServerDump_windowIntM1(self):
856873
vc.findViewByIdOrRaise('id/home')
857874
finally:
858875
device.shutdownMockViewServer()
859-
876+
860877
def testFindViewsContainingPoint_api15(self):
861878
try:
862879
device = MockDevice(version=15, startviewserver=True)
@@ -870,7 +887,24 @@ def testFindViewsContainingPoint_api15(self):
870887
def testFindViewsContainingPoint_api17(self):
871888
device = MockDevice(version=17)
872889
vc = ViewClient(device, device.serialno, adb=TRUE)
873-
list = vc.findViewsContainingPoint((200, 200))
890+
list = vc.findViewsContainingPoint((55, 75))
891+
self.assertNotEquals(None, list)
892+
self.assertNotEquals(0, len(list))
893+
894+
def testFindViewsContainingPoint_filterApi15(self):
895+
try:
896+
device = MockDevice(version=15, startviewserver=True)
897+
vc = ViewClient(device, device.serialno, adb=TRUE)
898+
list = vc.findViewsContainingPoint((200, 200), filter=View.isClickable)
899+
self.assertNotEquals(None, list)
900+
self.assertNotEquals(0, len(list))
901+
finally:
902+
device.shutdownMockViewServer()
903+
904+
def testFindViewsContainingPoint_filterApi17(self):
905+
device = MockDevice(version=17)
906+
vc = ViewClient(device, device.serialno, adb=TRUE)
907+
list = vc.findViewsContainingPoint((55, 75), filter=View.isClickable)
874908
self.assertNotEquals(None, list)
875909
self.assertNotEquals(0, len(list))
876910

0 commit comments

Comments
 (0)