예제 #1
0
 def _get_current_window(self):
     """使用dumpsys命令获取当前窗口
     """
     if self.adb.get_sdk_version() >= 29:
         result = self.adb.run_shell_cmd("dumpsys window visible-apps")
     else:
         result = self.adb.run_shell_cmd("dumpsys window")
     if result:
         line_list = result.split("\n")
         for line in line_list:
             if "mCurrentFocus" in line or "mFocusedWindow" in line:
                 result = line
                 break
     pattern1 = re.compile(r"mCurrentFocus=Window{(.+)}")
     pattern2 = re.compile(r"mFocusedWindow=Window{(.+)}")
     for pattern in (pattern1, pattern2):
         ret = pattern.search(result)
         if ret:
             break
     else:
         logger.info("Get current window by dumpsys failed: %s" % result)
         return None
     result = ret.group(1).split(" ")[-1]
     if "/" in result:
         result = result.split("/")[-1]
     if "Application Not Responding" in ret.group(1):
         result = "Application Not Responding: %s" % result
     return result
예제 #2
0
 def _work_thread(self):
     time0 = time.time()
     while self._running and time.time() - time0 < self._timeout:
         infds, outfds, errfds = select.select([
             self._sock,
         ], [], [], 1)
         if len(infds) > 0:
             try:
                 buff = self._sock.recv(4096)
                 if len(buff) == 0:
                     self._sock.close()
                     self._sock = None
                     self._running = False
                     self._event.set()
                     return
                 self._stdout.write(buff)
             except socket.error as e:
                 logger.info("Recv response error: %s" % (e))
                 self._stdout.write(b" ")  # 通知接收方退出
                 self._sock.close()
                 self._sock = None
                 self._running = False
                 self._event.set()
                 return
     self._sock.close()
     self._sock = None
예제 #3
0
 def _work_thread(self):
     time0 = time.time()
     while self._running and time.time() - time0 < self._timeout:
         infds, outfds, errfds = select.select([
             self._sock,
         ], [], [], 1)
         if len(infds) > 0:
             try:
                 buff = self._sock.recv(4096)
                 if len(buff) == 0:
                     self._sock.close()
                     self._sock = None
                     self._running = False
                     self._event.set()
                     return
                 self._stdout.write(buff)
             except socket.error as e:
                 logger.info("接收返回数据错误: %s" % (e))
                 #                    import traceback
                 #                    traceback.print_exc()
                 self._stdout.write(b' ')  # 通知接收方退出
                 self._sock.close()
                 self._sock = None
                 self._running = False
                 self._event.set()
                 return
     self._sock.close()
     self._sock = None
예제 #4
0
    def send(self, data):
        if not self._connect:
            if not self.connect(): return None
        try:
            self._sock.send(data.encode('utf8'))
        except socket.error as e:
            logger.info('发送%r错误: %s' % (data, e))
            self._sock.close()
            self._connect = False
            return None

        expect_len = self.recv(8)
        if not expect_len: return None

        expect_len = int(expect_len, 16) + 1
        recv_buff = ''
        while len(recv_buff) < expect_len:
            buff = self.recv(expect_len - len(recv_buff))
            if not buff:
                logger.warn('Socket closed when recv rsp for %r' % data)

            while True:
                try:
                    recv_buff += buff.decode('utf8')
                    break
                except UnicodeDecodeError:
                    buff += self.recv(1)

        return recv_buff
예제 #5
0
    def run_server(self, server_name=''):
        '''运行测试桩进程,创建服务端
        '''
        if server_name == '': server_name = self.service_name
        from qt4a.androiddriver.androiddriver import AndroidDriver
        port = AndroidDriver.get_process_name_hash(server_name, self.get_device_id())
        logger.info('[DeviceDriver] port is %d' % port)
        addr = '127.0.0.1'
        if not self._is_local_device: addr = self.adb.host_name
        if self._client: self._client.close()
        self._client = None
        
        server_type = 'localabstract'
        if self.adb.is_rooted() and self.adb.is_selinux_opened():
            # 创建TCP服务端
            server_name = str(self.service_port)
            server_type = 'tcp'

        time0 = time.time()
        timeout = 20
        
        while time.time() - time0 < timeout:
            self._client = self._create_client(server_name, server_type)
            if self._client: return self._client
            ret = self._run_server(server_name)
            logger.debug('[DeviceDriver] Server %s process created: %s' % (server_name, ret))
            time.sleep(0.1)
        raise RuntimeError('连接系统测试桩超时')
예제 #6
0
 def drag(self,
          x1,
          y1,
          x2,
          y2,
          count=5,
          wait_time=40,
          send_down_event=True,
          send_up_event=True):
     '''滑动
     :param x1: 起始横坐标
     :type x1:  int
     :param y1: 起始纵坐标
     :type y1:  int
     :param x2: 终点横坐标
     :type x2:  int
     :param y2: 终点纵坐标
     :type y2:  int
     :param count: 步数
     :type count: int
     :param wait_time: 滑动过程中每步之间的间隔时间,ms
     :type wait_time: int
     :param send_down_event: 是否发送按下消息
     :type send_down_event: boolean
     :param send_up_event: 是否发送弹起消息
     :type send_up_event: boolean 
     '''
     if y1 != y2 and abs(y1 - y2) < 60:  # 此时滑动距离会很短
         # 保证最短滑动距离为40,在索尼ce0682上发现小于40时会变成点击 三星9300上发现有时60像素以下滑动不起来
         m = (y1 + y2) // 2
         if y1 - y2 > 0:
             d = 30
         else:
             d = -30
         y1 = m + d  # TODO:坐标合法性判断
         y2 = m - d
     for _ in range(3):
         try:
             result = self.send_command(EnumCommand.CmdDrag,
                                        X1=x1,
                                        Y1=y1,
                                        X2=x2,
                                        Y2=y2,
                                        StepCount=count,
                                        SleepTime=wait_time,
                                        SendDownEvent=send_down_event,
                                        SendUpEvent=send_up_event)
         except AndroidSpyError as e:
             if str(e).find('java.lang.SecurityException') >= 0:
                 logger.info(
                     'java.lang.SecurityException,current activity:%s' %
                     self._device_driver.get_current_activity())  # 检测是否有弹窗
                 # 有时操作过快会出现该问题
                 time.sleep(0.1)
             else:
                 raise e
         else:
             return True
     logger.error('drag (%s, %s, %s, %s) failed' % (x1, y1, x2, y2))
     return False
예제 #7
0
 def is_debug_package(self, package_name):
     '''是否是debug包
     '''
     ret = self.run_driver_cmd('isDebugPackage', package_name, root=self.adb.is_rooted())
     logger.info('isDebugPackage ret: %s' % ret)
     if 'NameNotFoundException' in ret:
         raise RuntimeError('APP: %s not installed' % package_name)
     return 'true' in ret
예제 #8
0
 def post_test(self):
     '''清理测试用例
     '''
     from qt4a.androiddriver.util import logger
     logger.info('post_test run')
     super(DemoTestBase, self).post_test()
     Device.release_all_device()  # 释放所有设备
     logger.info('postTest complete')
예제 #9
0
    def post_test(self):
        '''清理测试用例
        '''

        logger.info('开始执行测试清理。。。')
        super(GYTestBase, self).post_test()
        Device.release_all_device()  # 释放所有设备
        logger.info('测试清理执行完毕。')
예제 #10
0
 def _server_opend(self):
     """判断测试桩进程是否运行
     """
     process_name = self.service_name + ":service"
     pid = self.adb.get_pid(process_name)
     if pid != 0 and pid != self._server_pid:
         logger.info("[DeviceDriver] %s pid is %d" % (process_name, pid))
     self._server_pid = pid
     return pid > 0
예제 #11
0
 def get_sim_card_state(self):
     '''获取sim卡状态
     '''
     sim_state = self.adb.get_property('gsm.sim.state').strip()
     if sim_state == 'READY': return 'SIM_STATE_READY'
     if 'ABSENT' in sim_state or 'NOT_READY' in sim_state:
         return 'SIM_STATE_ABSENT'
     logger.info('sim state: %s' % sim_state)
     return 'SIM_STATE_UNKNOWN'
예제 #12
0
 def _server_opend(self):
     '''判断测试桩进程是否运行
     '''
     process_name = self.service_name + ':service'
     pid = self.adb.get_pid(process_name)
     if pid != 0 and pid != self._server_pid:
         logger.info('[DeviceDriver] %s pid is %d' % (process_name, pid))
     self._server_pid = pid
     return pid > 0
예제 #13
0
 def send_back_key(self):
     '''发送返回按键
     '''
     if self.device.is_rooted():
         self.device.send_key(4)
     else:
         res = self.device.send_key(4)
         if not res:
             logger.info('send key 4 return False')
             self.run_shell_cmd('input keyevent 4')
예제 #14
0
 def is_debug_package(self, package_name):
     """是否是debug包
     """
     ret = self.run_driver_cmd("isDebugPackage",
                               package_name,
                               root=self.adb.is_rooted())
     logger.info("isDebugPackage ret: %s" % ret)
     if "NameNotFoundException" in ret:
         raise RuntimeError("APP: %s not installed" % package_name)
     return "true" in ret
예제 #15
0
    def acquire_device(self, type='Android', device_id='', **kwds):
        '''申请设备

        :param type: 申请的设备类型,目前尚未使用
        :type type:  string
        :param device_id: 申请的设备ID,默认不指定设备ID
        :type device_id:  string
        '''
        logger.info('开始申请设备。。。')
        device = super(GYTestBase, self).acquire_device(device_id, **kwds)
        device.adb.start_logcat([])
        logger.info('设备名称%s' % str(device.device_list))
        return device
예제 #16
0
 def install_package(self, pkg_path, overwrite=False):
     '''安装应用
     '''
     from qt4a.androiddriver.util import get_file_md5
     if not os.path.exists(pkg_path):
         raise RuntimeError('APK: %r not exist' % pkg_path)
     
     pkg_size = os.path.getsize(pkg_path)
     pkg_md5 = get_file_md5(pkg_path)
     pkg_name = ADB._get_package_name(pkg_path)
     if self.is_package_installed(pkg_name, pkg_size, pkg_md5):
         logger.info('APP %s [%d]%s is installed' % (pkg_name, pkg_size, pkg_md5))
         return True
     
     self.adb.install_apk(pkg_path, overwrite)
     return True
예제 #17
0
    def open_app(self, app_name, force_by_search=False):
        '''打开小程序
        '''
        found = False
        if not force_by_search:
            timeout = 2
            time0 = time.time()
            while time.time() - time0 < timeout:
                if len(self.Controls["小程序列表"]) > 2:
                    break
                time.sleep(0.5)

            for i in range(3):
                logger.info('第%d次尝试查找小程序' % i)
                for i in range(1, len(self.Controls["小程序列表"])):
                    it = self.Controls["小程序列表"][i]
                    if it.has('小程序名称'):
                        if general_encode(it['小程序名称'].text) == general_encode(app_name):
                            it.click()
                            found = True
                            break
                if found:
                    break
                time.sleep(1)
        if not found:
            # 通过搜索进入
            self.Controls["搜索"].click()
            search_panel = MiniProgramSearchPanel(self)
            search_panel.open_app(app_name)

        try:
            self._app.wait_for_activity(TBSDownloadDialog.Activity)
        except:
            pass
        else:
            dialog = TBSDownloadDialog(self)
            if dialog.exist():
                dialog.upgrade()
            time.sleep(4)

        app_panel = MiniProgramPanel(self)
        try:
            app_panel.wait_for_exist()
        except ControlNotFoundError:
            return self.open_app(app_name)
        else:
            return app_panel
예제 #18
0
    def send(self, data):
        if not self._connect:
            if not self.connect(): return None
        try:
            self._sock.send(data.encode('utf8'))
        except socket.error as e:
            logger.info('发送%r错误: %s' % (data, e))
            self._sock.close()
            self._connect = False
            return None

        expect_len = self.recv(8)
        if not expect_len: return None

        expect_len = int(expect_len, 16) + 1
        recv_buff = ''
        max_utf8_length = 6

        while len(recv_buff) < expect_len:
            buff = self.recv(expect_len - len(recv_buff))

            if not buff:
                logger.warn('Socket closed when recv rsp for %r' % data)

            while True:
                try:
                    recv_buff += buff.decode('utf8')
                    break
                except UnicodeDecodeError:
                    if len(buff) > max_utf8_length:
                        for i in range(max_utf8_length):
                            this_buff = buff[:i - max_utf8_length]
                            try:
                                recv_buff += this_buff.decode('utf8')
                            except UnicodeDecodeError:
                                pass
                            else:
                                buff = buff[i - max_utf8_length:]
                                break
                        else:
                            raise RuntimeError('Invalid utf-8 bytes: %r' %
                                               buff)
                    buff += self.recv(1)

        return recv_buff
예제 #19
0
    def hello(self):
        '''
        '''
        result = self._client.hello()
        if result == None: return
        if not 'Result' in result:
            logger.warn('[DeviceDriver] no Result in hello rsp')
            raise RuntimeError('Server error')
        logger.info('[DeviceDriver] %s' % result['Result'])
        items = result['Result'].split(':')

        if len(items) > 0 and items[-1].isdigit():
            if self._server_pid == 0:
                logger.warn('[DeviceDriver] Server pid is 0')
            elif self._server_pid != 0 and int(items[-1]) != self._server_pid:
                logger.warn('[DeviceDriver] Server pid not match: %s' % (int(items[-1])))
                raise RuntimeError('Server error %s' % result['Result'])
        return result
예제 #20
0
    def send_command(self, cmd_type, **kwds):
        """发送命令
        """
        curr_thread_id = threading.current_thread().ident
        self._wait_for_event(curr_thread_id, self._max_block_time)

        if cmd_type != EnumCommand.CmdHello:
            self._safe_init_driver()  # 确保测试桩连接正常

        result = self._client.send_command(cmd_type, **kwds)
        if result == None:
            pid = self._adb.get_pid(self._process["name"])
            if pid > 0 and pid != self._process["id"]:
                # 新进程,需要重新注入
                logger.info("process %s id changed: %d %d" %
                            (self._process["name"], self._process["id"], pid))
                self._is_init = False  # 需要重新初始化
                self._process["id"] = pid
                return self.send_command(cmd_type, **kwds)
            elif pid == 0:
                raise ProcessExitError("被测进程已退出,确认是否发生Crash")
            elif cmd_type != EnumCommand.CmdHello:
                # hello包不重试
                logger.debug("socket error, try to reconnect")
                for _ in range(3):
                    # 为防止由于设备短暂失联导致的连接断开,这里调一次adb forward
                    self._client = self._create_client()
                    result = self._client.send_command(cmd_type, **kwds)
                    if result != None:
                        return result
                raise SocketError("Connect Failed")
            else:
                raise SocketError("Connect Failed")
        if "Error" in result:
            if result["Error"] == u"控件已失效" or result[
                    "Error"] == u"Control expired":
                raise ControlExpiredError(result["Error"])
            elif result["Error"] == u"控件类型错误":
                control_type = self.get_control_type(kwds["Control"])
                raise TypeError("%s,当前控件类型为:%s" %
                                (result["Error"], control_type))
            else:
                raise AndroidSpyError(result["Error"])
        return result
예제 #21
0
    def send_command(self, cmd_type, **kwds):
        '''发送命令
        '''
        curr_thread_id = threading.current_thread().ident
        self._wait_for_event(curr_thread_id, self._max_block_time)

        if cmd_type != EnumCommand.CmdHello:
            self._safe_init_driver()  # 确保测试桩连接正常

        result = self._client.send_command(cmd_type, **kwds)
        if result == None:
            pid = self._adb.get_pid(self._process['name'])
            if pid > 0 and pid != self._process['id']:
                # 新进程,需要重新注入
                logger.info('process %s id changed: %d %d' %
                            (self._process['name'], self._process['id'], pid))
                self._is_init = False  # 需要重新初始化
                self._process['id'] = pid
                return self.send_command(cmd_type, **kwds)
            elif pid == 0:
                raise ProcessExitError('被测进程已退出,确认是否发生Crash')
            elif cmd_type != EnumCommand.CmdHello:
                # hello包不重试
                logger.debug('socket error, try to reconnect')
                for _ in range(3):
                    # 为防止由于设备短暂失联导致的连接断开,这里调一次adb forward
                    self._client = self._create_client()
                    result = self._client.send_command(cmd_type, **kwds)
                    if result != None:
                        return result
                raise SocketError('Connect Failed')
            else:
                raise SocketError('Connect Failed')
        if 'Error' in result:
            if result['Error'] == u'控件已失效':
                raise ControlExpiredError(result['Error'])
            elif result['Error'] == u'控件类型错误':
                control_type = self.get_control_type(kwds['Control'])
                raise TypeError('%s,当前控件类型为:%s' %
                                (result['Error'], control_type))
            else:
                raise AndroidSpyError(result['Error'])
        return result
예제 #22
0
    def send_command(self, cmd_type, **kwds):
        '''send command
        '''
        packet = {}
        packet['Cmd'] = cmd_type
        packet['Seq'] = self.seq
        for key in kwds.keys():
            packet[key] = kwds[key]
        data = json.dumps(packet) + "\n"
        if six.PY2 and isinstance(data, unicode):
            data = data.encode('utf8')

        time0 = time_clock()
        self._lock.acquire()
        time1 = time_clock()
        delta = time1 - time0
        if self._enable_log and delta >= 0.05:
            logger.info('send wait %s S' % delta)
        if self._enable_log: logger.debug('send: %s' % (data[:512].strip()))

        time0 = time_clock()
        try:
            result = self.send(data)
        except Exception as e:
            # 避免因异常导致死锁
            logger.exception('send %r error: %s' % (data, e))
            result = None
        self._lock.release()  # 解锁
        if not result: return None

        time1 = time_clock()
        try:
            rsp = json.loads(result)
        except:
            logger.error('json error: %r' % (result))
            raise
        else:
            if self._enable_log:
                delta = int(1000 * (time1 - time0))
                if 'HandleTime' in rsp: delta -= rsp['HandleTime']
                logger.debug('recv: [%d]%s\n' % (delta, result[:512].strip()))
            return rsp
예제 #23
0
    def hello(self):
        """
        """
        result = self._client.hello()
        if result == None:
            return
        if not "Result" in result:
            logger.warn("[DeviceDriver] no Result in hello rsp")
            raise RuntimeError("Server error")
        logger.info("[DeviceDriver] %s" % result["Result"])
        items = result["Result"].split(":")

        if len(items) > 0 and items[-1].isdigit():
            if self._server_pid == 0:
                logger.warn("[DeviceDriver] Server pid is 0")
            elif self._server_pid != 0 and int(items[-1]) != self._server_pid:
                logger.warn("[DeviceDriver] Server pid not match: %s" %
                            (int(items[-1])))
                raise RuntimeError("Server error %s" % result["Result"])
        return result
예제 #24
0
 def _get_current_window(self):
     '''使用dumpsys命令获取当前窗口
     '''
     result = self.adb.run_shell_cmd('dumpsys window windows')
     if result:
         line_list = result.split('\n')
         for line in line_list:
             if 'mCurrentFocus' in line:
                 result = line
                 break
     pattern = re.compile(r'mCurrentFocus=Window{(.+)}')
     ret = pattern.search(result)
     if not ret:
         logger.info('Get current window by dumpsys failed: %s' % result)
         return None
     result = ret.group(1).split(' ')[-1]
     if '/' in result: result = result.split('/')[-1]
     if 'Application Not Responding' in ret.group(1):
         result = 'Application Not Responding: %s' % result
     return result
예제 #25
0
파일: device.py 프로젝트: Tencent/QT4A
 def read_logcat(self, tag, process_name_pattern, pattern, num=1):
     '''查找最近满足条件的一条log
     
     :param tag: 期望的Tag
     :type tag:  string
     :param process_name_pattern: 期望的进程名,传入正则表达式
     :type process_name_pattern:  string
     :param pattern:  期望匹配的格式
     :type pattern:   Pattern
     :param num:  返回满足条件的日志条数
     :type num:   int
     '''
     from qt4a.androiddriver.util import logger
     pat = re.compile(
         r'^\[(.+)\(\d+\)\]\s+\[.+\]\s+\w/(.+)\(\s*\d+\):\s+(.+)$')
     log_pat = re.compile(pattern)
     log_list = self.adb.get_log(False)
     log_list = [i.decode("utf-8") for i in log_list]
     result_list = []
     k = 0
     for i in range(len(log_list) - 1, -1, -1):
         ret = pat.match(log_list[i])
         if not ret:
             logger.info(
                 'read_logcat:%s not match ^\[(.+)\(\d+\)\]\s+\[.+\]\s+\w/(.+)\(\s*\d+\):\s+(.+)$'
                 % log_list[i])
             continue
         if not process_name_pattern: continue
         process_pat = re.compile(process_name_pattern)
         if not process_pat.match(ret.group(1)): continue
         if ret.group(2) != tag: continue
         if log_pat.search(ret.group(3)):
             if num == 1:
                 return ret.group(3)
             elif num == 0 or k < num:
                 k += 1
                 result_list.append(ret.group(3))
             else:
                 break
     return result_list
예제 #26
0
def copy_android_driver(device_id_or_adb,
                        force=False,
                        root_path=None,
                        enable_acc=True):
    '''测试前的测试桩拷贝
    '''
    from qt4a.androiddriver.adb import ADB
    from qt4a.androiddriver.util import AndroidPackage, version_cmp

    if isinstance(device_id_or_adb, ADB):
        adb = device_id_or_adb
    else:
        adb = ADB.open_device(device_id_or_adb)

    if not root_path:
        current_path = os.path.abspath(__file__)
        if not os.path.exists(current_path) and '.egg' in current_path:
            # in egg
            egg_path = current_path
            while not os.path.exists(egg_path):
                egg_path = os.path.dirname(egg_path)
            assert (egg_path.endswith('.egg'))
            root_path = os.path.join(tempfile.mkdtemp(), 'tools')
            extract_from_zipfile(egg_path, 'qt4a/androiddriver/tools',
                                 root_path)
        else:
            root_path = os.path.join(
                os.path.dirname(os.path.abspath(__file__)), 'tools')
    dst_path = '/data/local/tmp/qt4a/'

    current_version_file = os.path.join(root_path, 'version.txt')
    f = open(current_version_file, 'r')
    current_version = int(f.read())
    f.close()

    if not force:
        version_file = dst_path + 'version.txt'
        version = adb.run_shell_cmd('cat %s' % version_file)

        if version and not 'No such file or directory' in version and current_version <= int(
                version):
            # 不需要拷贝测试桩
            logger.warn('忽略本次测试桩拷贝:当前版本为%s,设备中版本为%s' %
                        (current_version, int(version)))
            return

    try:
        adb.chmod(dst_path[:-1], '777')
    except:
        pass

    rooted = adb.is_rooted()

    cpu_abi = adb.get_cpu_abi()
    print('Current CPU arch: %s' % cpu_abi)
    use_pie = False
    if adb.get_sdk_version() >= 21 and cpu_abi != 'arm64-v8a':
        use_pie = True

    file_list = [
        os.path.join(cpu_abi, 'droid_inject'),
        os.path.join(cpu_abi, 'libdexloader.so'),
        os.path.join(cpu_abi, 'screenkit'),
        os.path.join(cpu_abi, 'libandroidhook.so'), 'inject', 'AndroidSpy.jar',
        'SpyHelper.jar', 'SpyHelper.sh'
    ]

    if cpu_abi == 'arm64-v8a':
        file_list.append(os.path.join(cpu_abi, 'droid_inject64'))
        file_list.append(os.path.join(cpu_abi, 'libdexloader64.so'))
        file_list.append('inject64')
    if adb.get_sdk_version() >= 21:
        file_list.append(os.path.join(cpu_abi, 'libandroidhook_art.so'))

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        adb.run_shell_cmd('rm -r %s' % dst_path, True)
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibdexloader.so' % dst_path, True)  # 恢复文件context,否则拷贝失败
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibandroidhook.so' % dst_path, True)

    for file in file_list:
        file_path = os.path.join(root_path, file)
        if use_pie and not '.' in file and os.path.exists(file_path + '_pie'):
            file_path += '_pie'
        if not os.path.exists(file_path):
            continue
        save_name = os.path.split(file)[-1]
        if save_name.endswith('_art.so'):
            save_name = save_name.replace('_art', '')
        adb.push_file(file_path, dst_path + save_name)

    adb.chmod('%sdroid_inject' % dst_path, 755)
    adb.chmod('%sinject' % dst_path, 755)
    adb.chmod('%sscreenkit' % dst_path, 755)
    adb.run_shell_cmd('ln -s %sscreenkit %sscreenshot' % (dst_path, dst_path))

    if cpu_abi == 'arm64-v8a':
        adb.chmod('%sdroid_inject64' % dst_path, 755)
        adb.chmod('%sinject64' % dst_path, 755)

    try:
        print(adb.run_shell_cmd('rm -R %scache' % dst_path,
                                rooted))  # 删除目录 rm -rf
    except RuntimeError as e:
        logger.warn('%s' % e)
    # logger.info(adb.run_shell_cmd('mkdir %scache' % (dst_path), True)) #必须使用root权限,不然生成odex文件会失败

    adb.mkdir('%scache' % (dst_path), 777)

    qt4a_helper_package = 'com.test.androidspy'
    apk_path = os.path.join(root_path, 'QT4AHelper.apk')
    if adb.get_package_path(qt4a_helper_package):
        # 判断版本
        installed_version = adb.get_package_version(qt4a_helper_package)
        package = AndroidPackage(apk_path)
        install_version = package.version
        if version_cmp(install_version, installed_version) > 0:
            adb.install_apk(apk_path, True)
    else:
        adb.install_apk(apk_path)

    # adb.install_apk(os.path.join(root_path, 'QT4AMockApp.apk'), True)
    version_file_path = os.path.join(root_path, 'version.txt')
    dst_version_file_path = dst_path + os.path.split(version_file_path)[-1]
    adb.push_file(version_file_path, dst_version_file_path + '.tmp')  # 拷贝版本文件

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        # 获取sdcars context
        if adb.get_sdk_version() >= 23:
            import re
            sdcard_path = adb.get_sdcard_path()
            result = adb.run_shell_cmd('ls -Z %s' % sdcard_path)
            # u:object_r:media_rw_data_file:s0 u:object_r:rootfs:s0
            pattern = re.compile(r'\s+(u:object_r:.+:s0)\s+')
            ret = pattern.search(result)
            if not ret:
                raise RuntimeError('get sdcard context failed: %s' % result)
            context = ret.group(1)
            logger.info('sdcard context is %s' % context)
            adb.run_shell_cmd('chcon %s %s' % (context, dst_path),
                              True)  # make app access
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %sSpyHelper.jar' % dst_path,
                True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %sSpyHelper.sh' % dst_path,
                True)
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                'chcon u:object_r:system_file:s0 %slibdexloader.so' % dst_path,
                True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %slibandroidhook.so' %
                dst_path, True)
            adb.run_shell_cmd(
                'chcon %s %sAndroidSpy.jar' % (context, dst_path), True)
            adb.run_shell_cmd('chcon %s %scache' % (context, dst_path), True)
        else:
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %slibdexloader.so' %
                dst_path, True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %slibandroidhook.so' %
                dst_path, True)
            adb.run_shell_cmd(
                'chcon u:object_r:app_data_file:s0 %scache' % dst_path, True)

    if rooted:
        if adb.get_sdk_version() < 24:
            # 7.0以上发现生成的dex与运行时生成的dex有差别,可能导致crash
            logger.info(
                adb.run_shell_cmd(
                    'sh %sSpyHelper.sh loadDex %sAndroidSpy.jar %scache' %
                    (dst_path, dst_path, dst_path), rooted))
            adb.chmod('%scache/AndroidSpy.dex' % dst_path, 666)
    else:
        if not 'usage:' in adb.run_shell_cmd('sh %sSpyHelper.sh' % dst_path):
            adb.mkdir('%scache/dalvik-cache' % dst_path, 777)

    if rooted and adb.is_selinux_opened() and adb.get_sdk_version() >= 23:
        # 提升权限
        try:
            adb.list_dir('/system/bin/app_process32')
        except RuntimeError:
            adb.copy_file('/system/bin/app_process',
                          '%sapp_process' % dst_path)
        else:
            adb.copy_file('/system/bin/app_process32',
                          '%sapp_process' % dst_path)
        adb.chmod('%sapp_process' % dst_path, 755)
        adb.run_shell_cmd(
            'chcon u:object_r:system_file:s0 %sapp_process' % dst_path, True)

    adb.run_shell_cmd(
        'mv %s %s' % (dst_version_file_path + '.tmp', dst_version_file_path),
        rooted)

    # 同步手机时间
    device_driver = DeviceDriver(adb)
    try:
        input_method = 'com.test.androidspy/.service.QT4AKeyboardService'
        device_driver.modify_system_setting('secure', 'enabled_input_methods',
                                            input_method)
        device_driver.modify_system_setting('secure', 'default_input_method',
                                            input_method)
        if enable_acc:
            device_driver.modify_system_setting(
                'secure', 'enabled_accessibility_services',
                'com.test.androidspy/com.test.androidspy.service.QT4AAccessibilityService'
            )
            device_driver.modify_system_setting('secure',
                                                'accessibility_enabled', 1)
    except:
        logger.exception('set default input method failed')
    try:
        device_driver.modify_system_setting('system', 'time_12_24', 24)
        device_driver.modify_system_setting('system', 'screen_off_timeout',
                                            600 * 1000)
    except:
        logger.exception('set system time failed')
예제 #27
0
 def post_test(self):
     '''清理测试用例
     '''
     logger.info('postTest run')
     super(WxTestBase, self).post_test()
     logger.info('postTest complete')
예제 #28
0
    def _init_driver(self):
        """初始化测试桩
        """
        self._client = self._create_client()
        if self._client != None:
            # 字段赋值
            self._process["name"] = self._process_name
            self._process["id"] = 0  # process id may change
            if self.hello() != None:
                self._process["id"] = self._adb.get_pid(self._process_name)
                return

        timeout = 20
        time0 = time.time()
        proc_exist = False
        while time.time() - time0 < timeout:
            if not proc_exist:
                pid = self._adb.get_pid(self._process_name)
                if pid > 0:
                    proc_exist = True
                    self._process["name"] = self._process_name
                    self._process["id"] = pid
                    break

            time.sleep(1)

        if not proc_exist:
            raise RuntimeError("进程:%s 在%d秒内没有出现" %
                               (self._process_name, timeout))

        inject_file = "inject"
        if self._adb.is_app_process64(
                pid if self._adb.is_rooted() else self._process_name):
            # 64 bit process
            inject_file += "64"
        timeout = 30

        try:
            if self._adb.is_art():
                # Android 5.0上发现注入容易导致进程退出
                self._wait_for_cpu_low(20, 10)

            time0 = time.time()
            cmdline = "%s/%s %s" % (
                self._get_driver_root_path(),
                inject_file,
                self._process_name,
            )
            while time.time() - time0 < timeout:
                if self._adb.is_rooted():
                    ret = self._adb.run_shell_cmd(cmdline,
                                                  True,
                                                  timeout=120,
                                                  retry_count=1)
                else:
                    ret = self._adb.run_as(self._process_name,
                                           cmdline,
                                           timeout=120,
                                           retry_count=1)
                logger.debug("inject result: %s" % ret)
                if "not found" in ret:
                    raise QT4ADriverNotInstalled(
                        "QT4A driver damaged, please reinstall QT4A driver")
                if "Inject Success" in ret:
                    break
                elif "Operation not permitted" in ret:
                    # 可能是进程处于Trace状态
                    pid = self._adb.get_pid(self._process_name)
                    status = self._adb.get_process_status(pid)
                    tracer_pid = int(status["TracerPid"])
                    if tracer_pid > 0:
                        if int(status["PPid"]) == tracer_pid:
                            # 使用TRACEME方式防注入
                            raise Exception("应用使用了防注入逻辑,注入失败")
                        logger.warn("TracerPid is %d" % tracer_pid)
                        self._adb.kill_process(tracer_pid)
                elif "Function not implemented" in ret:
                    raise Exception(
                        "Please install repacked app on this device")
                time.sleep(1)

        except RuntimeError as e:
            logger.exception("%s\n%s" % (e, self._adb.run_shell_cmd("ps")))
            if self._adb.is_rooted():
                logger.info(self._adb.dump_stack(self._process_name))
            raise e
        timeout = 10
        time0 = time.time()
        while time.time() - time0 < timeout:
            if self._client == None:
                self._client = self._create_client()
            if self._client != None and self.hello() != None:
                return
            time.sleep(0.1)
        raise RuntimeError("连接测试桩超时")
예제 #29
0
def copy_android_driver(device_id_or_adb,
                        force=False,
                        root_path=None,
                        enable_acc=True):
    """测试前的测试桩拷贝
    """
    from qt4a.androiddriver.adb import ADB

    if isinstance(device_id_or_adb, ADB):
        adb = device_id_or_adb
    else:
        adb = ADB.open_device(device_id_or_adb)

    if not root_path:
        current_path = os.path.abspath(__file__)
        if not os.path.exists(current_path) and ".egg" in current_path:
            # in egg
            egg_path = current_path
            while not os.path.exists(egg_path):
                egg_path = os.path.dirname(egg_path)
            assert egg_path.endswith(".egg")
            root_path = os.path.join(tempfile.mkdtemp(), "tools")
            extract_from_zipfile(egg_path, "qt4a/androiddriver/tools",
                                 root_path)
        else:
            root_path = os.path.join(
                os.path.dirname(os.path.abspath(__file__)), "tools")
    dst_path = "/data/local/tmp/qt4a/"

    current_version_file = os.path.join(root_path, "version.txt")
    f = open(current_version_file, "r")
    current_version = int(f.read())
    f.close()

    if not force:
        version_file = dst_path + "version.txt"
        version = adb.run_shell_cmd("cat %s" % version_file)

        if (version and not "No such file or directory" in version
                and current_version <= int(version)):
            install_qt4a_helper(adb, root_path)  # 避免QT4A助手被意外删除的情况
            # 不需要拷贝测试桩
            logger.warn("忽略本次测试桩拷贝:当前版本为%s,设备中版本为%s" %
                        (current_version, int(version)))
            return

    try:
        adb.chmod(dst_path[:-1], "777")
    except:
        pass

    rooted = adb.is_rooted()

    cpu_abi = adb.get_cpu_abi()
    print("Current CPU arch: %s" % cpu_abi)
    # use_pie = False
    # if adb.get_sdk_version() >= 21 and cpu_abi != "arm64-v8a":
    #     use_pie = True

    file_list = [
        os.path.join(cpu_abi, "droid_inject"),
        os.path.join(cpu_abi, "libdexloader.so"),
        os.path.join(cpu_abi, "screenkit"),
        "inject",
        "AndroidSpy.jar",
        "SpyHelper.jar",
        "SpyHelper.sh",
    ]
    if cpu_abi == "arm64-v8a":
        file_list.append(os.path.join(cpu_abi, "droid_inject64"))
        file_list.append(os.path.join(cpu_abi, "libdexloader64.so"))
        file_list.append("inject64")
    if adb.get_sdk_version() >= 21:
        file_list.append(os.path.join(cpu_abi, "libandroidhook_art.so"))

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        adb.run_shell_cmd("rm -r %s" % dst_path, True)
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibdexloader.so' % dst_path, True)  # 恢复文件context,否则拷贝失败
        # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibandroidhook.so' % dst_path, True)

    for file in file_list:
        file_path = os.path.join(root_path, file)
        # if use_pie and not "." in file and os.path.exists(file_path + "_pie"):
        #     file_path += "_pie"
        if not os.path.exists(file_path):
            continue
        save_name = os.path.split(file)[-1]
        if save_name.endswith("_art.so"):
            save_name = save_name.replace("_art", "")
        adb.push_file(file_path, dst_path + save_name)

    adb.chmod("%sdroid_inject" % dst_path, 755)
    adb.chmod("%sinject" % dst_path, 755)
    adb.chmod("%sscreenkit" % dst_path, 755)
    adb.run_shell_cmd("ln -s %sscreenkit %sscreenshot" % (dst_path, dst_path))

    if cpu_abi == "arm64-v8a":
        adb.chmod("%sdroid_inject64" % dst_path, 755)
        adb.chmod("%sinject64" % dst_path, 755)

    try:
        print(adb.run_shell_cmd("rm -R %scache" % dst_path,
                                rooted))  # 删除目录 rm -rf
    except RuntimeError as e:
        logger.warn("%s" % e)
    # logger.info(adb.run_shell_cmd('mkdir %scache' % (dst_path), True)) #必须使用root权限,不然生成odex文件会失败

    adb.mkdir("%scache" % (dst_path), 777)

    install_qt4a_helper(adb, root_path)

    version_file_path = os.path.join(root_path, "version.txt")
    dst_version_file_path = dst_path + os.path.split(version_file_path)[-1]
    adb.push_file(version_file_path, dst_version_file_path + ".tmp")  # 拷贝版本文件

    if rooted and adb.is_selinux_opened():
        # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面
        # 获取sdcars context
        if adb.get_sdk_version() >= 23:
            sdcard_path = adb.get_sdcard_path()
            result = adb.run_shell_cmd("ls -Z %s" % sdcard_path)
            # u:object_r:media_rw_data_file:s0 u:object_r:rootfs:s0
            pattern = re.compile(r"\s+(u:object_r:.+:s0)\s+")
            ret = pattern.search(result)
            if not ret:
                raise RuntimeError("get sdcard context failed: %s" % result)
            context = ret.group(1)
            logger.info("sdcard context is %s" % context)
            adb.run_shell_cmd("chcon %s %s" % (context, dst_path),
                              True)  # make app access
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %sSpyHelper.jar" % dst_path,
                True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %sSpyHelper.sh" % dst_path,
                True)
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                "chcon u:object_r:system_file:s0 %slibdexloader.so" % dst_path,
                True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %slibandroidhook.so" %
                dst_path, True)
            adb.run_shell_cmd(
                "chcon %s %sAndroidSpy.jar" % (context, dst_path), True)
            adb.run_shell_cmd("chcon %s %scache" % (context, dst_path), True)
        else:
            # 不修改文件context无法加载so
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %slibdexloader.so" %
                dst_path, True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %slibandroidhook.so" %
                dst_path, True)
            adb.run_shell_cmd(
                "chcon u:object_r:app_data_file:s0 %scache" % dst_path, True)

    if rooted:
        if adb.get_sdk_version() < 24:
            # 7.0以上发现生成的dex与运行时生成的dex有差别,可能导致crash
            logger.info(
                adb.run_shell_cmd(
                    "sh %sSpyHelper.sh loadDex %sAndroidSpy.jar %scache" %
                    (dst_path, dst_path, dst_path),
                    rooted,
                ))
            adb.chmod("%scache/AndroidSpy.dex" % dst_path, 666)
    else:
        if not "usage:" in adb.run_shell_cmd("sh %sSpyHelper.sh" % dst_path):
            adb.mkdir("%scache/dalvik-cache" % dst_path, 777)

    if rooted and adb.is_selinux_opened() and adb.get_sdk_version() >= 23:
        # 提升权限
        try:
            adb.list_dir("/system/bin/app_process32")
        except RuntimeError:
            adb.copy_file("/system/bin/app_process",
                          "%sapp_process" % dst_path)
        else:
            adb.copy_file("/system/bin/app_process32",
                          "%sapp_process" % dst_path)
        adb.chmod("%sapp_process" % dst_path, 755)
        adb.run_shell_cmd(
            "chcon u:object_r:system_file:s0 %sapp_process" % dst_path, True)

    adb.run_shell_cmd(
        "mv %s %s" % (dst_version_file_path + ".tmp", dst_version_file_path),
        rooted)

    # 同步手机时间
    device_driver = DeviceDriver(adb)
    try:
        input_method = "com.test.androidspy/.service.QT4AKeyboardService"
        device_driver.modify_system_setting("secure", "enabled_input_methods",
                                            input_method)
        device_driver.modify_system_setting("secure", "default_input_method",
                                            input_method)
        if enable_acc:
            device_driver.modify_system_setting(
                "secure",
                "enabled_accessibility_services",
                "com.test.androidspy/com.test.androidspy.service.QT4AAccessibilityService",
            )
            device_driver.modify_system_setting("secure",
                                                "accessibility_enabled", 1)
    except:
        logger.exception("set default input method failed")
    try:
        device_driver.modify_system_setting("system", "time_12_24", 24)
        device_driver.modify_system_setting("system", "screen_off_timeout",
                                            600 * 1000)
    except:
        logger.exception("set system time failed")
예제 #30
0
    def _init_driver(self):
        '''初始化测试桩
        '''
        self._client = self._create_client()
        if self._client != None:
            # 字段赋值
            self._process['name'] = self._process_name
            self._process['id'] = 0  # process id may change
            if self.hello() != None:
                self._process['id'] = self._adb.get_pid(self._process_name)
                return

        timeout = 20
        time0 = time.time()
        proc_exist = False
        while time.time() - time0 < timeout:
            if not proc_exist:
                pid = self._adb.get_pid(self._process_name)
                if pid > 0:
                    proc_exist = True
                    self._process['name'] = self._process_name
                    self._process['id'] = pid
                    break

            time.sleep(1)

        if not proc_exist:
            raise RuntimeError('进程:%s 在%d秒内没有出现' %
                               (self._process_name, timeout))

        inject_file = 'inject'
        if self._adb.is_app_process64(
                pid if self._adb.is_rooted() else self._process_name):
            # 64 bit process
            inject_file += '64'
        timeout = 30

        try:
            if self._adb.is_art():
                # Android 5.0上发现注入容易导致进程退出
                self._wait_for_cpu_low(20, 10)

            time0 = time.time()
            cmdline = '%s/%s %s' % (self._get_driver_root_path(), inject_file,
                                    self._process_name)
            while time.time() - time0 < timeout:
                if self._adb.is_rooted():
                    ret = self._adb.run_shell_cmd(cmdline,
                                                  True,
                                                  timeout=120,
                                                  retry_count=1)
                else:
                    ret = self._adb.run_as(self._process_name,
                                           cmdline,
                                           timeout=120,
                                           retry_count=1)
                logger.debug('inject result: %s' % ret)
                if 'not found' in ret:
                    raise QT4ADriverNotInstalled(
                        'QT4A driver damaged, please reinstall QT4A driver')
                if 'Inject Success' in ret:
                    break
                elif 'Operation not permitted' in ret:
                    # 可能是进程处于Trace状态
                    pid = self._adb.get_pid(self._process_name)
                    status = self._adb.get_process_status(pid)
                    tracer_pid = int(status['TracerPid'])
                    if tracer_pid > 0:
                        if int(status['PPid']) == tracer_pid:
                            # 使用TRACEME方式防注入
                            raise Exception('应用使用了防注入逻辑,注入失败')
                        logger.warn('TracerPid is %d' % tracer_pid)
                        self._adb.kill_process(tracer_pid)
                time.sleep(1)

        except RuntimeError as e:
            logger.exception('%s\n%s' % (e, self._adb.run_shell_cmd('ps')))
            if self._adb.is_rooted():
                logger.info(self._adb.dump_stack(self._process_name))
            raise e
        timeout = 10
        time0 = time.time()
        while time.time() - time0 < timeout:
            if self._client == None:
                self._client = self._create_client()
            if self._client != None and self.hello() != None:
                return
            time.sleep(0.1)
        raise RuntimeError('连接测试桩超时')