123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- # coding: utf-8
- #
- from __future__ import absolute_import, print_function
- import argparse
- import hashlib
- import logging
- import os
- import re
- import shutil
- import sys
- import tarfile
- import time
- import fire
- import humanize
- import progress.bar
- import requests
- from logzero import logger
- from retry import retry
- import ssat_sdk.uiautomator2 as u2
- from ssat_sdk.uiautomator2 import adbutils
- from ssat_sdk.uiautomator2.version import __apk_version__, __atx_agent_version__
- appdir = os.path.join(os.path.expanduser("~"), '.uiautomator2')
- logger.debug("use cache directory: %s", appdir)
- GITHUB_BASEURL = "https://github.com/openatx"
- class DownloadBar(progress.bar.Bar):
- message = "Downloading"
- suffix = '%(current_size)s / %(total_size)s'
- @property
- def total_size(self):
- return humanize.naturalsize(self.max, gnu=True)
- @property
- def current_size(self):
- return humanize.naturalsize(self.index, gnu=True)
- def cache_download(url, filename=None):
- """ return downloaded filepath """
- # check cache
- if not filename:
- filename = os.path.basename(url)
- storepath = os.path.join(appdir,
- hashlib.sha224(url.encode()).hexdigest(),
- filename)
- storedir = os.path.dirname(storepath)
- if not os.path.isdir(storedir):
- os.makedirs(storedir)
- if os.path.exists(storepath) and os.path.getsize(storepath) > 0:
- return storepath
- # download from url
- headers = {
- 'Accept': '*/*',
- 'Accept-Encoding': 'gzip, deflate, br',
- 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
- 'Connection': 'keep-alive',
- 'Origin': 'https://github.com',
- 'User-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
- } # yapf: disable
- r = requests.get(url, stream=True, headers=headers)
- if r.status_code != 200:
- raise Exception(url, "status code", r.status_code)
- file_size = int(r.headers.get("Content-Length"))
- bar = DownloadBar(filename, max=file_size)
- with open(storepath + '.tmp', 'wb') as f:
- chunk_length = 16 * 1024
- while 1:
- buf = r.raw.read(chunk_length)
- if not buf:
- break
- f.write(buf)
- bar.next(len(buf))
- bar.finish()
- shutil.move(storepath + '.tmp', storepath)
- return storepath
- class Initer():
- def __init__(self, device):
- logger.info(">>> Initial device %s", device)
- d = self._device = device
- self.sdk = d.getprop('ro.build.version.sdk')
- self.abi = d.getprop('ro.product.cpu.abi')
- self.pre = d.getprop('ro.build.version.preview_sdk')
- self.arch = d.getprop('ro.arch')
- self.abis = (d.getprop('ro.product.cpu.abilist').strip()
- or self.abi).split(",")
- self.server_addr = None
- def shell(self, *args):
- logger.debug("Shell: %s", args)
- return self._device.shell_output(*args)
- @property
- def apk_urls(self):
- for name in ["app-uiautomator.apk", "app-uiautomator-test.apk"]:
- yield "".join([
- GITHUB_BASEURL,
- "/android-uiautomator-server/releases/download/",
- __apk_version__, "/", name
- ])
- @property
- def atx_agent_url(self):
- files = {
- 'armeabi-v7a': 'atx-agent_{v}_linux_armv7.tar.gz',
- 'arm64-v8a': 'atx-agent_{v}_linux_armv7.tar.gz',
- 'armeabi': 'atx-agent_{v}_linux_armv6.tar.gz',
- 'x86': 'atx-agent_{v}_linux_386.tar.gz',
- }
- name = None
- for abi in self.abis:
- name = files.get(abi)
- if name:
- break
- if not name:
- raise Exception(
- "arch(%s) need to be supported yet, please report an issue in github"
- % self.abis)
- return GITHUB_BASEURL + '/atx-agent/releases/download/%s/%s' % (
- __atx_agent_version__, name.format(v=__atx_agent_version__))
- @property
- def minicap_urls(self):
- base_url = GITHUB_BASEURL + \
- "/stf-binaries/raw/master/node_modules/minicap-prebuilt/prebuilt/"
- sdk = self.sdk
- yield base_url + self.abi + "/lib/android-" + sdk + "/minicap.so"
- yield base_url + self.abi + "/bin/minicap"
- @property
- def minitouch_url(self):
- return ''.join([
- GITHUB_BASEURL + "/stf-binaries",
- "/raw/master/node_modules/minitouch-prebuilt/prebuilt/",
- self.abi + "/bin/minitouch"
- ])
- def push_url(self, url, dest=None, mode=0o755, tgz=False, extract_name=None): # yapf: disable
- path = cache_download(url, os.path.basename(url))
- if tgz:
- tar = tarfile.open(path, 'r:gz')
- path = os.path.join(os.path.dirname(path), extract_name)
- tar.extract(extract_name, os.path.dirname(path))
- if not dest:
- dest = "/data/local/tmp/" + os.path.basename(path)
- logger.debug("Push %s -> %s:0%o", url, dest, mode)
- self._device.sync.push(path, dest, mode=mode)
- return dest
- def is_apk_outdate(self):
- apk1 = self._device.package_info("com.github.uiautomator")
- if not apk1:
- return True
- if apk1['version_name'] != __apk_version__:
- return True
- if not self._device.package_info("com.github.uiautomator.test"):
- return True
- return False
- def install(self, server_addr=None):
- logger.info("Install minicap, minitouch")
- self.push_url(self.minitouch_url)
- if self.abi == "x86":
- logger.info(
- "abi:x86 seems to be android emulator, skip install minicap")
- else:
- for url in self.minicap_urls:
- self.push_url(url)
- logger.info(
- "Install com.github.uiautomator, com.github.uiautomator.test")
- if self.is_apk_outdate():
- self.shell("pm", "uninstall", "com.github.uiautomator")
- self.shell("pm", "uninstall", "com.github.uiautomator.test")
- for url in self.apk_urls:
- path = self.push_url(url, mode=0o644)
- self.shell("pm", "install", "-r", "-t", path)
- else:
- logger.info("Already installed com.github.uiautomator apks")
- logger.info("Install atx-agent")
- path = self.push_url(
- self.atx_agent_url, tgz=True, extract_name="atx-agent")
- args = [path, "server", "-d"]
- if server_addr:
- args.extend(['-t', server_addr])
- self.shell(path, "server", "--stop")
- self.shell(*args)
- logger.info("Check install")
- self.check_atx_agent_version()
- print("Successfully init %s" % self._device)
- @retry(
- (requests.ConnectionError, requests.ReadTimeout, requests.HTTPError),
- delay=.5,
- tries=10)
- def check_atx_agent_version(self):
- port = self._device.forward_port(7912)
- logger.debug("Forward: local:tcp:%d -> remote:tcp:%d", port, 7912)
- response = requests.get("http://127.0.0.1:%d/version" % port).text
- logger.debug("atx-agent version %s", response.strip())
- # def check_apk_installed(self, apk_version):
- # """ in OPPO device, if you check immediatelly, package_info will return None """
- # pkg_info = self.package_info("com.github.uiautomator")
- # if not pkg_info:
- # raise EnvironmentError(
- # "package com.github.uiautomator not installed")
- # if pkg_info['version_name'] != apk_version:
- # raise EnvironmentError(
- # "package com.github.uiautomator version expect \"%s\" got \"%s\""
- # % (apk_version, pkg_info['version_name']))
- # r = requests.get(
- # 'http://localhost:%d/version' % lport, timeout=10)
- # r.raise_for_status()
- # log.info("atx-agent version: %s", r.text)
- # # todo finish the retry logic
- # print("atx-agent output:", output.strip())
- # # open uiautomator2 github URL
- # self.shell("am", "start", "-a", "android.intent.action.VIEW",
- # "-d", "https://github.com/openatx/uiautomator2")
- class MyFire(object):
- def update_apk(self, ip):
- """ update com.github.uiautomator apk remotely """
- u = u2.connect(ip)
- apk_version = __apk_version__
- app_url = GITHUB_BASEURL + \
- '/android-uiautomator-server/releases/download/%s/app-uiautomator.apk' % apk_version
- app_test_url = GITHUB_BASEURL + \
- '/android-uiautomator-server/releases/download/%s/app-uiautomator-test.apk' % apk_version
- u.app_install(app_url)
- u.app_install(app_test_url)
- def clear_cache(self):
- logger.info("clear cache dir: %s", appdir)
- shutil.rmtree(appdir, ignore_errors=True)
- def install(self, arg1, arg2=None):
- """
- Example:
- install "http://some-host.apk"
- install "$serial" "http://some-host.apk"
- """
- if arg2 is None:
- device_ip, apk_url = None, arg1
- else:
- device_ip, apk_url = arg1, arg2
- u = u2.connect(device_ip)
- pkg_name = u.app_install(apk_url)
- print("Installed", pkg_name)
- def unlock(self, device_ip=None):
- u = u2.connect(device_ip)
- u.unlock()
- def app_stop_all(self, device_ip=None):
- u = u2.connect(device_ip)
- u.app_stop_all()
- def uninstall_all(self, device_ip=None):
- u = u2.connect(device_ip)
- u.app_uninstall_all(verbose=True)
- def identify(self, device_ip=None, theme='black'):
- u = u2.connect(device_ip)
- u.open_identify(theme)
- def screenshot(self, device_ip, filename):
- u = u2.connect(device_ip)
- u.screenshot(filename)
- def healthcheck(self, device_ip):
- u = u2.connect(device_ip)
- u.healthcheck()
- def cmd_init(args):
- if args.serial:
- device = adbutils.adb.device_with_serial(args.serial)
- init = Initer(device)
- init.install(args.server)
- else:
- for device in adbutils.adb.iter_device():
- init = Initer(device)
- init.install(args.server)
- def cmd_screenshot(args):
- raise NotImplementedError()
- _commands = [
- dict(
- action=cmd_init,
- command="init",
- help="install enssential resources to device",
- flags=[
- dict(args=['--serial', '-s'], type=str, help='serial number'),
- dict(name=['server'], type=str, help='atxserver address')
- ]),
- dict(
- action=cmd_screenshot,
- command="screenshot",
- help="not implemented",
- flags=[
- dict(args=['-o', '--output'], type=str, help="output filename")
- ])
- ]
- def main():
- # yapf: disable
- if True or len(sys.argv) >= 2 and sys.argv[1] in ('init', 'screenshot'):
- parser = argparse.ArgumentParser(
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- parser.add_argument('-s', '--serial', type=str,
- help='device serial number')
- subparser = parser.add_subparsers(dest='subparser')
- actions = {}
- for c in _commands:
- cmd_name = c['command']
- actions[cmd_name] = c['action']
- sp = subparser.add_parser(cmd_name, help=c.get('help'),
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- for f in c.get('flags', []):
- args = f.get('args')
- if not args:
- args = ['-'*min(2, len(n)) + n for n in f['name']]
- kwargs = f.copy()
- kwargs.pop('name', None)
- kwargs.pop('args', None)
- sp.add_argument(*args, **kwargs)
- args = parser.parse_args()
- print(args, args.subparser)
- if args.subparser:
- actions[args.subparser](args)
- return
- # yapf: enable
- fire.Fire(MyFire)
- if __name__ == '__main__':
- # import logzero
- # logzero.loglevel(logging.INFO)
- main()
|