text: import os
  from typing import List, Set
  import xml.etree.ElementTree as ET
  from xml.etree.ElementTree import Element
  
  import xlsxwriter
  
  
  def dump_ui_xml() -> str:
      os.system('adb shell uiautomator dump')
      os.system('adb pull /sdcard/window_dump.xml')
      os.system('adb shell rm /sdcard/window_dump.xml')
      return os.path.abspath('./window_dump.xml')
  
  
  def send_scroll_down() -> None:
      os.system('adb shell input swipe 500 1000 500 500')
  
  
  def get_header(ui_xml_path: str) -> List[str]:
      tree = ET.parse(ui_xml_path)
      root = tree.getroot()
      header = root.find('.//node[@class="android.widget.ListView"]/../node[1]')
      assert isinstance(header, Element)
      header_texts = [node.get('text') for node in header.findall('node')]
      # for debug
      print(' | '.join(header_texts))
      return header_texts
  
  
  def get_listview_items(ui_xml_path: str) -> List[List[str]]:
      tree = ET.parse(ui_xml_path)
      root = tree.getroot()
      listview = root.find('.//node[@class="android.widget.ListView"]')
      assert isinstance(listview, Element)
      ## UI structure ----------------------------
      ############################################
      # ListView
      # |- LinearLayout        (a Row)
      #    |- LinearLayout
      #       |- View
      #       |- LinearLayout  (Content of a Row)
      #          |- TextView   (a Column)
      #          |- View       (a Column separator?)
      #          |- TextView   (a Column)
      #          |- ...
      # |- LinearLayout        (a Row)
      # |- ...
      #-------------------------------------------
  
      # There is two type of LinearLayout (a Row)
      ## Type 1
      #
      # |- LinearLayout        (a Row)
      #    |- LinearLayout
      #       |- View
      #       |- LinearLayout  (Content of a Row)
      #          |- TextView   (a Column)
      #          |- View       (a Column separator?)
      #          |- TextView   (a Column)
      #          |- ...
      #
      ## Type 2
      #
      # |- LinearLayout        (a Row)
      #    |- LinearLayout
      #       |- View       (a Column separator?)
      #       |- TextView   (a Column)
      #       |- TextView   (a Column)
      #       |- View       (a Column separator?)
      columns = []
      for item in listview:
          row = item.find('./node[1]')
          texts = [
              column.get('text')
              for column in row.findall('.//node[@class="android.widget.TextView"]')  # noqa: E501
          ]  # type: List[str]
          columns.append(texts)
          # for debug
          print(' | '.join(texts))
          print('-' * 80)
      return columns
  
  
  def dump() -> List[List[str]]:
      # get_column_headers('window_dump.xml')
      keys = set()  # type: Set[str]
      report = []   # type: List[List[str]]
      header = []  # type: List[str]
  
      while True:
          if not header:
              header = get_header(dump_ui_xml())
              report.append(header)
          columns = get_listview_items(dump_ui_xml())  # type: List[List[str]]
          key_columns = []
          for column in columns:
              key_column = column[0]
              key_columns.append(key_column)
              if key_column not in keys:
                  report.append(column)
          if keys.issuperset(key_columns):
              break
          keys.update(key_columns)
          send_scroll_down()
      return report
  
  
  def write_report_to_xlsx(report: List[List[str]], filename='report.xlsx'):
      workbook = xlsxwriter.Workbook(filename)
      worksheet = workbook.add_worksheet()
      row = 0
      col = 0
      for row_data in report:
          worksheet.write_row(row, col, row_data)
          row += 1
      workbook.close()
  
  
  if __name__ == '__main__':
      write_report_to_xlsx(dump())
  