apkcrawler: limit clickable elements.
Only click on certain types of UI elements that are onscreen.
Change-Id: Ib4c207755f55d10127e2ce4eadc2f51d560190d3
diff --git a/crawlui.py b/crawlui.py
index 1eed9a5..b9ac9e3 100644
--- a/crawlui.py
+++ b/crawlui.py
@@ -7,9 +7,20 @@
import time
# Linux ADB path
-#ADB_PATH = os.path.expanduser('~') + '/Android/Sdk/platform-tools/adb'
+ADB_PATH = os.path.expanduser('~') + '/Android/Sdk/platform-tools/adb'
# OS X ADB path
-ADB_PATH = '/usr/local/bin/adb'
+#ADB_PATH = '/usr/local/bin/adb'
+
+MAX_WIDTH = 1080
+# TODO(afergan): For layouts longer than the width of the screen, scroll down
+# and click on them. For now, we ignore them.
+MAX_HEIGHT = 1920
+NAVBAR_HEIGHT = 63
+
+# Visibility
+VISIBLE = 0x0
+INVISIBLE = 0x4
+GONE = 0x8
from com.dtmilano.android.viewclient import ViewClient
from subprocess import check_output
@@ -50,6 +61,10 @@
'\'Local FragmentActivity\''], stdout =
subprocess.PIPE, stderr = subprocess.PIPE)
fragment_str, err = proc.communicate()
+
+ fragment_name = re.search('Local FragmentActivity (.*?) State:',fragment_str)
+ if fragment_name is None:
+ return 'NoFrag'
return re.search('Local FragmentActivity (.*?) State:',fragment_str).group(1)
def save_screenshot(package_name):
@@ -78,7 +93,6 @@
if view_array[i].is_duplicate(get_activity_name(),
get_fragment_name(package_name), vc_dump):
return i
-
return -1
def create_view(package_name, vc_dump):
@@ -86,9 +100,29 @@
v.hierarchy = vc_dump
for component in v.hierarchy:
- print component['uniqueId']
- if component.isClickable():
- v.clickable.append(component)
+ # TODO(afergan): For now, only click on certain components, and allow custom
+ # components. Evaluate later if this is worth it or if we should just click
+ # on everything attributed as clickable.
+
+ # TODO(afergan): Should we include clickable ImageViews? Seems like a lot of
+ # false clicks for this so far...
+ if (component.isClickable() and component.getVisibility() == VISIBLE and
+ component.getX() >= 2 and component.getY() <= MAX_WIDTH and
+ component['layout:layout_width'] > 0 and
+ component.getY() >= (NAVBAR_HEIGHT + 2) and
+ component.getY() <= MAX_HEIGHT and
+ component['layout:layout_height'] > 0 and
+ ('Button' in component['class'] or
+ (component ['class'] ==
+ 'com.android.settings.dashboard.DashboardTileView') or
+ component['class'] == 'android.widget.ImageView' or
+ 'ActionMenuItemView' in component['class'] or
+ 'TextView' in component['class'] or
+ 'android' not in component['class'] or
+ 'Spinner' in component['class'] or
+ component.parent == 'android.widget.ListView')):
+ print component['class'] + '-- will be clicked'
+ v.clickable.append(component)
screenshot_info = save_screenshot(package_name)
v.screenshot = screenshot_info[0]
@@ -109,17 +143,22 @@
curr_view = create_view(package_name, vc_dump)
view_array.append(curr_view)
+ print 'Num clickable: ' + str(len(curr_view.clickable))
+
if len(curr_view.clickable) > 0:
c = curr_view.clickable[0]
- print 'Clickable: ' + c['uniqueId'] + ' ' + c['class'] + str(c.getXY())
- subprocess.call([ADB_PATH, 'shell', 'input', 'tap', str(c.getXY()[0]),
- str(c.getXY()[1])])
- # time.sleep(1)
+ print c
+ print ('Clickable: ' + c['uniqueId'] + ' ' + c['class'] + ' ' +
+ str(c.getX()) + ' ' + str(c.getY()))
+ subprocess.call([ADB_PATH, 'shell', 'input', 'tap', str(c.getX()),
+ str(c.getY())])
print str(len(curr_view.clickable)) + ' elements left to click'
del curr_view.clickable[0]
else:
print '!!! Clicking back button'
+ if curr_view == view_root:
+ return
perform_press_back()
crawl_activity(package_name, vc, device)
@@ -129,7 +168,6 @@
# Install the app.
subprocess.call([ADB_PATH, 'install', '-r', apk_dir + package_name
+ '.apk'])
-
#Launch the app.
subprocess.call([ADB_PATH, 'shell', 'monkey', '-p', package_name, '-c',
'android.intent.category.LAUNCHER', '1'])