apkcrawler: Store fragment list.
Use the developer-provided fragments instead of the system fragment
name. Since each fragment may be composed of multiple fragments, we
store them all in a list, but use the first one for file naming
purposes.
Change-Id: I584309de7c12b6c1c89d688e5bd878026fae0d53
diff --git a/crawlui.py b/crawlui.py
index add0233..4d9b5b1 100644
--- a/crawlui.py
+++ b/crawlui.py
@@ -7,9 +7,9 @@
from view import View
# 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
@@ -22,6 +22,10 @@
INVISIBLE = 0x4
GONE = 0x8
+# Return a unique string if the package is not the focused window. Since
+# activities cannot have spaces, we ensure that no activity will be named this.
+EXITED_APP = 'exited app'
+
view_root = []
view_array = []
@@ -30,10 +34,10 @@
subprocess.call([ADB_PATH, 'shell', 'input', 'keyevent', '4'])
-def get_activity_name():
+def get_activity_name(package_name):
"""Gets the current running activity of the package."""
- # TODO(afergan): Make sure we are still running the correct package and have
- # not exited or redirected to a different app.
+ # TODO(afergan): See if we can consolidate this with get_fragment_list, but
+ # still make sure that the current app has focus.
proc = subprocess.Popen([ADB_PATH, 'shell', 'dumpsys window windows '
'| grep -E \'mCurrentFocus\''],
@@ -46,6 +50,14 @@
popup_str = activity_str[activity_str.find('PopupWindow'):].split('}')[0]
return popup_str.replace(':', '')
+ # We are no longer in the app.
+ if package_name not in activity_str:
+ print 'Exited app'
+ # If app opened a different app, try to get back to it.
+ perform_press_back()
+ if package_name not in activity_str:
+ return EXITED_APP
+
# The current focus returns a string in the format
# mCurrentFocus=Window{35f66c3 u0 com.google.zagat/com.google.android.apps.
# zagat.activities.BrowseListsActivity}
@@ -53,32 +65,31 @@
return activity_str.split('.')[-1].split('}')[0]
-def get_fragment_name(package_name):
- """Gets the current top fragment of the package."""
- proc = subprocess.Popen([ADB_PATH, 'shell', 'dumpsys activity ',
- package_name, ' | grep -E '
- '\'Local FragmentActivity\''],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- fragment_str, _ = proc.communicate()
-
- fragment_name = re.search('Local FragmentActivity (.*?) State:', fragment_str)
- if fragment_name is None:
+def get_frag_list(package_name):
+ """Gets the list of fragments in the current view."""
+ proc = subprocess.Popen([ADB_PATH, 'shell', 'dumpsys activity', package_name],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ adb_dump, _ = proc.communicate()
+ frag_dump = re.findall('Added Fragments:(.*?)FragmentManager', adb_dump,
+ re.DOTALL)
+ if not frag_dump:
return 'NoFrag'
- return re.search('Local FragmentActivity (.*?) State:', fragment_str).group(1)
+ frag_list = re.findall(': (.*?){', frag_dump[0], re.DOTALL)
+ print frag_list
+ return frag_list
-def save_screenshot(package_name, activity, fragment):
+def save_screenshot(package_name, activity, frag_list):
"""Store the screenshot with a unique filename."""
directory = (os.path.dirname(os.path.abspath(__file__)) + '/data/'
+ package_name)
if not os.path.exists(directory):
os.makedirs(directory)
screenshot_num = 0
- while os.path.exists(directory + '/' + activity + '-' + fragment + '-' +
+ while os.path.exists(directory + '/' + activity + '-' + frag_list[0] + '-' +
str(screenshot_num) + '.png'):
screenshot_num += 1
- screen_name = activity + '-' + fragment + '-' + str(screenshot_num) + '.png'
+ screen_name = activity + '-' + frag_list[0] + '-' + str(screenshot_num) + '.png'
print screen_name
screen_path = directory + '/' + screen_name
subprocess.call([ADB_PATH, 'shell', 'screencap', '/sdcard/' + screen_name])
@@ -89,18 +100,19 @@
return [screen_path, screenshot_num]
-def find_view_idx(vc_dump, activity, fragment):
+def find_view_idx(vc_dump, activity, frag_list):
+ """Find the index of the current View in the view array (-1 if new view)."""
for i in range(len(view_array)):
if view_array[i].is_duplicate(activity,
- fragment, vc_dump):
+ frag_list, vc_dump):
return i
return -1
-def create_view(package_name, vc_dump, activity, fragment):
+def create_view(package_name, vc_dump, activity, frag_list, debug):
"""Store the current view in the View data structure."""
- v = View(activity, fragment)
- v.hierarchy = vc_dump
+ screenshot_info = save_screenshot(package_name, activity, frag_list)
+ v = View(activity, frag_list, vc_dump,screenshot_info[0], screenshot_info[1])
for component in v.hierarchy:
# TODO(afergan): For now, only click on certain components, and allow custom
@@ -126,14 +138,14 @@
'com.android.settings.dashboard.DashboardTileView') or
component['class'] == 'android.widget.ImageView' or
component.parent == 'android.widget.ListView'
- 'android' not in component['class'])):
+ 'android' not in component['class']) and
+ # TODO(afergan): Remove this.
+ # For debugging purposes, get rid of the phantom clickable components
+ # that Zagat creates.
+ not (debug and component.getXY() == (0, 273))):
print component['class'] + '-- will be clicked'
v.clickable.append(component)
- screenshot_info = save_screenshot(package_name, activity, fragment)
- v.screenshot = screenshot_info[0]
- v.num = screenshot_info[1]
-
return v
@@ -151,11 +163,13 @@
# Store the root View
print 'Storing root'
vc_dump = vc.dump(window='-1')
- activity = get_activity_name()
- fragment = get_fragment_name(package_name)
+ activity = get_activity_name(package_name)
+ if activity == EXITED_APP:
+ return
+ frag_list = get_frag_list(package_name)
global view_root
view_root = create_view(package_name, vc_dump, activity,
- fragment)
+ frag_list, debug)
view_array.append(view_root)
while True:
@@ -164,13 +178,13 @@
perform_press_back()
# Determine if this is a View that has already been seen.
- view_idx = find_view_idx(vc_dump, activity, fragment)
+ view_idx = find_view_idx(vc_dump, activity, frag_list)
if view_idx >= 0:
print '**FOUND DUPLICATE'
curr_view = view_array[view_idx]
else:
print '**NEW VIEW'
- curr_view = create_view(package_name, vc_dump, activity, fragment)
+ curr_view = create_view(package_name, vc_dump, activity, frag_list, debug)
view_array.append(curr_view)
print 'Num clickable: ' + str(len(curr_view.clickable))
@@ -192,5 +206,8 @@
return
vc_dump = vc.dump(window='-1')
- activity = get_activity_name()
- fragment = get_fragment_name(package_name)
+ activity = get_activity_name(package_name)
+ if activity == EXITED_APP:
+ return
+ frag_list = get_frag_list(package_name)
+
diff --git a/view.py b/view.py
index 98e7474..6863b63 100644
--- a/view.py
+++ b/view.py
@@ -10,26 +10,32 @@
components and their resulting views.
"""
- def __init__(self, activity, fragment):
+ def __init__(self, activity, frag_list, hierarchy, screenshot, num):
self.activity = activity
- self.fragment = fragment
- self.num = 0
- self.screenshot = ''
- self.hierarchy = []
+ self.frag_list = frag_list
+ self.hierarchy = hierarchy
+ self.screenshot = screenshot
+ self.num = num
self.clickable = []
self.preceding = []
def get_name(self):
- # Return the identifying name of the View (activity, fragment, and number).
- return [self.activity, self.fragment, self.num]
+ # Return the identifying name of the View (activity, fragment list, and
+ # number).
+ return [self.activity, self.frag_list, self.num]
def num_components(self):
return len(self.hierarchy)
- def is_duplicate(self, cv_activity, cv_fragment, cv_hierarchy):
+ def is_duplicate(self, cv_activity, cv_frag_list, cv_hierarchy):
"""Determine if the passed-in current view is identical to this View."""
- if self.activity != cv_activity or self.fragment != cv_fragment:
+ # Since the fragment names are hashable, this is the most efficient method to
+ # compare two unordered lists according to
+ # http://stackoverflow.com/questions/7828867/how-to-efficiently-compare-two-unordered-lists-not-sets-in-python
+ # We also use it below to compare hierarchy ids.
+ if (self.activity != cv_activity or
+ Counter(self.frag_list) != Counter(cv_frag_list)):
return False
if len(cv_hierarchy) != self.num_components():
@@ -38,15 +44,12 @@
hierarchy_ids = [h['uniqueId'] for h in self.hierarchy]
curr_view_ids = [cv['uniqueId'] for cv in cv_hierarchy]
- # Since the unique ids are hashable, this is the most efficient method to
- # compare two unordered lists according to
- # http://stackoverflow.com/questions/7828867/how-to-efficiently-compare-two-unordered-lists-not-sets-in-python
return Counter(hierarchy_ids) == Counter(curr_view_ids)
def print_info(self):
print 'Activity: ' + self.activity
- print 'Fragment: ' + self.fragment
+ print 'Fragment: ' + self.frag_list
print 'Num: " + str(self.num)'
print 'Screenshot path:' + self.screenshot
print 'Hierarchy: '