capsule: Catch socket timeouts for vc.dump().

Catch socket timeouts so that failed dumps do not crash the program, and
handle them appropriately.

Change-Id: I39eec27fa684180050aa88dcaf683f9be01c3883
diff --git a/capsule/crawlui.py b/capsule/crawlui.py
index dc689fd..ed943d5 100644
--- a/capsule/crawlui.py
+++ b/capsule/crawlui.py
@@ -31,6 +31,9 @@
 # How many times we should try pressing the back button to return to the app
 # before giving up.
 NUM_BACK_PRESSES = 3
+# Number of dumps we'll try in a row before succumbing to socket timeouts and
+# giving up.
+MAX_DUMPS = 6
 
 
 def extract_between(text, sub1, sub2, nth=1):
@@ -47,19 +50,32 @@
 def set_device_dimens(vc, device):
   """Sets global variables to the dimensions of the device."""
   global MAX_HEIGHT, MAX_WIDTH, NAVBAR_HEIGHT
-  vc_dump = vc.dump(window='-1')
+
   # Returns a string similar to "Physical size: 1440x2560"
   size = device.shell('wm size')
   MAX_HEIGHT = int(extract_between(size, 'x', '\r'))
   MAX_WIDTH = int(extract_between(size, ': ', 'x'))
-  NAVBAR_HEIGHT = (
-      vc_dump[0].getY() - int(vc_dump[0]['layout:getLocationOnScreen_y()']))
+  vc_dump = perform_vc_dump(vc)
+  if vc_dump:
+    NAVBAR_HEIGHT = (
+        vc_dump[0].getY() - int(vc_dump[0]['layout:getLocationOnScreen_y()']))
+  else:
+    # Keep navbar at default 0 height.
+    print 'Cannot get navbar height.'
 
 
 def perform_press_back(device):
   device.press('KEYCODE_BACK')
 
 
+def perform_vc_dump(vc):
+  try:
+    return vc.dump(window='-1')
+  except IOError:
+    print '*** Socket timeout!'
+    return None
+
+
 def return_to_app_activity(package_name, device):
   """Tries to press back a number of times to return to the app."""
 
@@ -327,51 +343,60 @@
         prev_clicked = BACK_BUTTON
 
     prev_view = curr_view
-    try:
-      vc_dump = vc.dump(window='-1')
-    except IOError:
-      print ('*** Socket timeout!')
-    curr_view = obtain_curr_view(activity, package_name, vc_dump, view_map,
-                                 device)
-    print 'Curr view: ' + curr_view.get_name()
-    if not prev_view.is_duplicate_view(curr_view):
-      print 'At a diff view!'
-      link_ui_views(prev_view, curr_view, prev_clicked, package_name)
+    vc_dump = perform_vc_dump(vc)
+    if vc_dump:
+      curr_view = obtain_curr_view(activity, package_name, vc_dump, view_map,
+                                   device)
+      print 'Curr view: ' + curr_view.get_name()
+      if not prev_view.is_duplicate_view(curr_view):
+        print 'At a diff view!'
+        link_ui_views(prev_view, curr_view, prev_clicked, package_name)
 
-    print 'Num clickable: ' + str(len(curr_view.clickable))
+      print 'Num clickable: ' + str(len(curr_view.clickable))
 
-    if curr_view.clickable:
-      c = curr_view.clickable[-1]
-      print('Clicking {} {}, ({},{})'.format(c.getUniqueId(), c.getClass(),
-                                             c.getX(), c.getY()))
-      c.touch()
-      prev_clicked = c.getUniqueId()
-      del curr_view.clickable[-1]
+      if curr_view.clickable:
+        c = curr_view.clickable[-1]
+        print('Clicking {} {}, ({},{})'.format(c.getUniqueId(), c.getClass(),
+                                               c.getX(), c.getY()))
+        c.touch()
+        prev_clicked = c.getUniqueId()
+        del curr_view.clickable[-1]
 
-    else:
-      print 'Clicking back button'
-      perform_press_back(device)
-      prev_view = curr_view
-      prev_clicked = BACK_BUTTON
-      activity = obtain_activity_name(package_name, device)
-
-      if activity is EXITED_APP:
-        activity = return_to_app_activity(package_name, device)
-        if activity is EXITED_APP:
-          print 'Clicking back took us out of the app'
-          break
-
-      # Make sure we have changed views.
-      vc_dump = vc.dump(window='-1')
-      curr_view = obtain_curr_view(activity, package_name, vc_dump,
-                                   view_map, device)
-      if prev_view.is_duplicate_view(curr_view):
-        # We have nothing left to click, and the back button doesn't change
-        # views.
-        print 'Pressing back keeps at the current view'
-        break
       else:
-        link_ui_views(prev_view, curr_view, 'back button', package_name)
+        print 'Clicking back button'
+        perform_press_back(device)
+        prev_view = curr_view
+        prev_clicked = BACK_BUTTON
+
+        # Make sure we have changed views.
+        vc_dump = perform_vc_dump(vc)
+        num_dumps = 0
+        while not vc_dump and num_dumps < MAX_DUMPS:
+          perform_press_back(device)
+          vc_dump = perform_vc_dump(vc)
+          num_dumps += 1
+
+        if num_dumps == MAX_DUMPS:
+          return
+
+        activity = obtain_activity_name(package_name, device)
+        if activity is EXITED_APP:
+          activity = return_to_app_activity(package_name, device)
+          if activity is EXITED_APP:
+            print 'Clicking back took us out of the app'
+            return
+
+        curr_view = obtain_curr_view(activity, package_name, vc_dump,
+                                     view_map, device)
+        if prev_view.is_duplicate_view(curr_view):
+          # We have nothing left to click, and the back button doesn't change
+          # views.
+          print 'Pressing back keeps at the current view'
+          return
+        else:
+          link_ui_views(prev_view, curr_view, 'back button', package_name)
+    else:
+      perform_press_back(device)
 
 
 def crawl_package(vc, device, package_name=None):
@@ -385,7 +410,9 @@
 
   # Store the root View
   print 'Storing root'
-  vc_dump = vc.dump(window='-1')
+  vc_dump = perform_vc_dump(vc)
+  if not vc_dump:
+    return
   activity = obtain_activity_name(package_name, device)
   view_root = obtain_curr_view(activity, package_name, vc_dump, view_map,
                                device)