apkcrawler: Store views.

We store each view in an array, and each time we arrive at a view we
check if the view already exists.

Also, store all clickable elements in a list so that we can remove
them from the list once we click on them.

Change-Id: Ie3ed030bd734489813d53bb1b7ad3b578dee42de
diff --git a/crawlui.py b/crawlui.py
index 061b844..1eed9a5 100644
--- a/crawlui.py
+++ b/crawlui.py
@@ -11,17 +11,32 @@
 # OS X ADB path
 ADB_PATH = '/usr/local/bin/adb'
 
-from com.dtmilano.android.viewclient import ViewClient, ViewClient
+from com.dtmilano.android.viewclient import ViewClient
 from subprocess import check_output
 from view import View
 
+view_root = []
+view_array = []
+
+def perform_press_back():
+  subprocess.call([ADB_PATH, 'shell', 'input', 'keyevent', '4'])
 
 def get_activity_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.
+
   proc = subprocess.Popen([ADB_PATH, 'shell', 'dumpsys window windows '
                           '| grep -E \'mCurrentFocus\''],
                           stdout = subprocess.PIPE, stderr = subprocess.PIPE)
   activity_str, err = proc.communicate()
+
+  # If a popup menu has captured the focus, the focus will be in the format
+  # mCurrentFocus=Window{8f1328e u0 PopupWindow:53a5957}
+  if 'PopupWindow' in activity_str:
+    popup_str = activity_str[activity_str.find('PopupWindow'):].split('}')[0]
+    return popup_str.replace(':','')
+
   # The current focus returns a string in the format
   # mCurrentFocus=Window{35f66c3 u0 com.google.zagat/com.google.android.apps.
   # zagat.activities.BrowseListsActivity}
@@ -35,7 +50,7 @@
                           '\'Local FragmentActivity\''], stdout =
                           subprocess.PIPE, stderr = subprocess.PIPE)
   fragment_str, err = proc.communicate()
-  return re.search("Local FragmentActivity (.*?) State:",fragment_str).group(1)
+  return re.search('Local FragmentActivity (.*?) State:',fragment_str).group(1)
 
 def save_screenshot(package_name):
   directory = (os.path.dirname(os.path.abspath(__file__)) + '/data/'
@@ -51,7 +66,6 @@
   screen_name = activity + '-' + fragment + '-' + str(screenshot_num) + '.png'
   print screen_name
   screen_path = directory + '/' + screen_name
-  print screen_path
   subprocess.call([ADB_PATH, 'shell', 'screencap', '/sdcard/' + screen_name])
   subprocess.call([ADB_PATH, 'pull', '/sdcard/' + screen_name, screen_path])
 
@@ -59,33 +73,58 @@
   # programatically.
   return [screen_path, screenshot_num]
 
-def crawl_activity(package_name, vc, device):
-  curr_view = vc.dump(window='-1')
-  clickable_components = []
+def find_view_idx(package_name, vc_dump):
+  for i in range(len(view_array)):
+    if view_array[i].is_duplicate(get_activity_name(),
+                                  get_fragment_name(package_name), vc_dump):
+      return i
 
-  for component in curr_view:
-    print component
+  return -1
+
+def create_view(package_name, vc_dump):
+  v = View(get_activity_name(), get_fragment_name(package_name))
+  v.hierarchy = vc_dump
+
+  for component in v.hierarchy:
+    print component['uniqueId']
     if component.isClickable():
-      clickable_components.append(component)
+      v.clickable.append(component)
 
-  for c in clickable_components:
-    print 'Clickable:' + c['uniqueId'] + ' ' + c['class'] + str(c.getXY())
+  screenshot_info = save_screenshot(package_name)
+  v.screenshot = screenshot_info[0]
+  v.num = screenshot_info[1]
+
+  return v
+
+def crawl_activity(package_name, vc, device):
+  vc_dump = vc.dump(window='-1')
+
+  # Returning to a view that has already been seen.
+  view_idx = find_view_idx(package_name, vc_dump)
+  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)
+    view_array.append(curr_view)
+
+  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)
+                   str(c.getXY()[1])])
+    # time.sleep(1)
+    print str(len(curr_view.clickable)) + ' elements left to click'
+    del curr_view.clickable[0]
 
-    # TODO (afergan): check for duplicates
-    v = View(get_activity_name(), get_fragment_name(package_name))
-    v.hierarchy = curr_view
-    screenshot_info = save_screenshot(package_name)
-    v.screenshot = screenshot_info[0]
-    v.num = screenshot_info[1]
-    v.print_info()
+  else:
+    print '!!! Clicking back button'
+    perform_press_back()
 
-    crawl_activity(package_name, vc, device)
+  crawl_activity(package_name, vc, device)
 
 def crawl_package(apk_dir, package_name, vc, device, debug):
-
   if (not(debug)):
     # Install the app.
     subprocess.call([ADB_PATH, 'install', '-r', apk_dir + package_name
@@ -95,4 +134,10 @@
     subprocess.call([ADB_PATH, 'shell', 'monkey', '-p', package_name, '-c',
                     'android.intent.category.LAUNCHER', '1'])
 
+  #Store the root View
+  print 'Storing root'
+  vc_dump = vc.dump(window='-1')
+  view_root = create_view(package_name, vc_dump)
+  view_array.append(view_root)
+
   crawl_activity(package_name, vc, device)
\ No newline at end of file
diff --git a/main.py b/main.py
index dbc6221..75c51b4 100644
--- a/main.py
+++ b/main.py
@@ -30,7 +30,7 @@
 if __name__ == '__main__':
 
   kwargs1 = {'verbose': True, 'ignoresecuredevice': True}
-  kwargs2 = {'startviewserver': True, 'forceviewserveruse': False,
+  kwargs2 = {'startviewserver': True, 'forceviewserveruse': True,
              'autodump': False, 'ignoreuiautomatorkilled': True}
   device, serialno = ViewClient.connectToDeviceOrExit(**kwargs1)
   vc = ViewClient(device, serialno, **kwargs2)
diff --git a/view.py b/view.py
index 56eb70d..834baec 100644
--- a/view.py
+++ b/view.py
@@ -33,7 +33,6 @@
       return False
 
     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