#!/usr/bin/env python
# -*-coding=utf8 -*-
"""
@version: v1.0
@author: jayzhen
@license: Apache Licence
@contact: jayzhen_testing@163.com
@site: http://blog.csdn.net/u013948858
@software: PyCharm
"""
import os
import platform
import re
import subprocess
import sys
import time
import string
import EventKeys
import json

reload(sys)
sys.setdefaultencoding('utf8')


class AdbCmder(object):
    """
    利用可变参数来初始化*(tuple),**(dict):约定参数中的key只能是sno
	a(1,2,3,4,4,Z=8,k=2) : *接受k=v之前的内容,**接受k=v
    """

    def __init__(self, **serialno_num):
        self.system = None
        self.find_type = None
        self.command = "adb"
        self.__serialno_num = ""
        if "sno" in serialno_num:
            self.__serialno_num = serialno_num.get("sno")

    def get_serialno_num(self):
        return self.__serialno_num

    def set_serialno_num(self, sno):
        self.__serialno_num = sno

    def judgment_system_type(self):
        # 判断系统类型,windows使用findstr,linux使用grep
        self.system = platform.system()
        if self.system is "Windows":
            self.find_type = "findstr"
        else:
            self.find_type = "grep"

    def judgment_system_environment_variables(self):
        self.judgment_system_type()
        # 判断是否设置环境变量ANDROID_HOME
        if "ANDROID_HOME" in os.environ:
            if self.system == "Windows":
                self.command = "adb"  # os.path.join(os.environ["ANDROID_HOME"], "platform-tools", "adb.exe")
            else:
                self.command = os.path.join(os.environ["ANDROID_HOME"], "platform-tools", "adb")
        else:
            raise EnvironmentError("Adb not found in $ANDROID_HOME path: %s." % os.environ["ANDROID_HOME"])

            # adb命令

    def adb(self, args):
        if self.__serialno_num == "" or self.__serialno_num is None:
            cmd = "%s %s" % (self.command, str(args))
        else:
            cmd = "%s -s %s %s" % (self.command, self.__serialno_num, str(args))
        return subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def shell(self, args):
        if self.__serialno_num == "" or self.__serialno_num is None:
            cmd = "%s shell %s" % (self.command, str(args))
        else:
            cmd = "%s -s %s shell %s" % (self.command, self.__serialno_num, str(args))
        return subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def get_device_state(self):
        """
        获取设备状态: offline | bootloader | device 等
        """
        return self.adb("get-state").stdout.read().strip()

    def get_device_sno(self):
        """
        只有一个设备,获取设备id号,return serialNo
        """
        return self.adb("get-serialno").stdout.read().strip()

    def get_device_list(self):
        devices = []
        result = subprocess.Popen("adb devices", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.readlines()
        result.reverse()  # 将readlines结果反向排序
        for line in result[1:]:
            if "attached" not in line.strip() and "daemon" not in line.strip():
                devices.append(line.split()[0])
            else:
                break
        return devices

    def get_android_os_version(self):
        """
        获取设备中的Android版本号,如4.2.2
        """
        return self.shell("getprop ro.build.version.release").stdout.read().strip()

    def get_sdk_version(self):
        """
        获取设备SDK版本号
        """
        return self.shell("getprop ro.build.version.sdk").stdout.read().strip()

    def get_device_model(self):
        """
        获取设备型号
        """
        return self.shell("getprop ro.product.model").stdout.read().strip()

    def get_app_pid(self, packageName):
        """
        获取进程pid
        args:
        - packageName -: 应用包名
        usage: getPid("com.android.settings")
        """
        if self.system is "Windows":
            pidinfo = self.shell("ps | findstr %s$" % packageName).stdout.read()
        else:
            pidinfo = self.shell("ps | grep -w %s" % packageName).stdout.read()

        if pidinfo == '':
            return "the process doesn't exist."

        pattern = re.compile(r"\d+")
        result = pidinfo.split(" ")
        result.remove(result[0])

        return  pattern.findall(" ".join(result))[0]

    def get_focused_package_and_activity_2(self):
        pattern = re.compile(r"[a-zA-Z0-9\.]+/.[a-zA-Z0-9\.]+")
        out = self.shell("dumpsys window w | %s \/ | %s name=" % (self.find_util, self.find_util)).stdout.read()
        return pattern.findall(out)[0]

    def get_focused_package_and_activity(self):
        """
        获取当前应用界面的包名和Activity,返回的字符串格式为:packageName/activityName
        """
        return self.shell("dumpsys activity | findstr mFocusedActivity").stdout.read().split()[-2]

    def get_current_package_name(self):
        """
         获取当前运行应用的activity
         """
        return self.get_focused_package_and_activity().split("/")[0]

    def get_current_activity(self):
        """
        获取当前设备的activity
        """
        return self.get_focused_package_and_activity().split("/")[-1]

    def get_battery_level(self):
        """
        获取电池电量
        """
        level = self.shell("dumpsys battery | %s level" %self.find_type).stdout.read().split(": ")[-1]
        return int(level)

    def get_battery_status(self):
        """
        获取电池充电状态
        BATTERY_STATUS_UNKNOWN:未知状态
        BATTERY_STATUS_CHARGING: 充电状态
        BATTERY_STATUS_DISCHARGING: 放电状态
        BATTERY_STATUS_NOT_CHARGING:未充电
        BATTERY_STATUS_FULL: 充电已满
        """
        statusDict = {1 : "BATTERY_STATUS_UNKNOWN",
                      2 : "BATTERY_STATUS_CHARGING",
                      3 : "BATTERY_STATUS_DISCHARGING",
                      4 : "BATTERY_STATUS_NOT_CHARGING",
                      5 : "BATTERY_STATUS_FULL"}
        status = self.shell("dumpsys battery | %s status" %self.find_type).stdout.read().split(": ")[-1]

        return statusDict[int(status)]

    def get_battery_temp(self):
        """
        获取电池温度
        """
        temp = self.shell("dumpsys battery | %s temperature" % self.find_type).stdout.read().split(": ")[-1]
        return int(temp) / 10.0

    def get_screen_resolution(self):
        """
        获取设备屏幕分辨率,return (width, high)
        """
        pattern = re.compile(r"\d+")
        out = self.shell("dumpsys display | %s PhysicalDisplayInfo" % self.find_type).stdout.read()
        display = pattern.findall(out)

        return (int(display[0]), int(display[1]))

    def get_system_app_list(self):
        """
        获取设备中安装的系统应用包名列表
        """
        sysApp = []
        for packages in self.shell("pm list packages -s").stdout.readlines():
            sysApp.append(packages.split(":")[-1].splitlines()[0])

        return sysApp

    def get_third_app_list(self):
        """
        获取设备中安装的第三方应用包名列表
        """
        thirdApp = []
        for packages in self.shell("pm list packages -3").stdout.readlines():
            thirdApp.append(packages.split(":")[-1].splitlines()[0])

        return thirdApp

    def get_matching_app_list(self, keyword):
        """
        模糊查询与keyword匹配的应用包名列表
        usage: getMatchingAppList("qq")
        """
        matApp = []
        for packages in self.shell("pm list packages %s" % keyword).stdout.readlines():
            matApp.append(packages.split(":")[-1].splitlines()[0])
        return matApp

    def get_app_start_total_time(self, component):
        """
        获取启动应用所花时间
        usage: getAppStartTotalTime("com.android.settings/.Settings")
        """
        time = self.shell("am start -W %s | %s TotalTime" % (component, self.find_type)) \
            .stdout.read().split(": ")[-1]
        return int(time)

    def do_install_app(self, appFile, pkg_name):
        """
        安装app,app名字不能含中文字符
        args:- appFile -: app路径
        usage: install("d:\\apps\\Weico.apk")
        """
        self.adb("install %s" % appFile)
        if not self.is_install_app(pkg_name):
            return True
        else:
            return False

    def do_uninstall_app(self, pkg_name):
        """
            卸载应用args:- packageName -:应用包名,非apk名
        """
        self.adb(" uninstall %s" % pkg_name)
        if not self.is_install_app(pkg_name):
            return True
        else:
            return False

    def is_install_app(self, packageName):
        """
        判断应用是否安装,已安装返回True,否则返回False
        usage: isInstall("com.example.apidemo")
        """
        flag = False
        result = self.get_third_app_list()
        if result is None or len(result) < 0:
            return None
        for i in result:
            if re.search(packageName, i.strip()):
                flag = True
        return flag

    def do_clear_app_data(self, packageName):
        """
        清除应用用户数据
        usage: clearAppData("com.android.contacts")
        """
        if "Success" in self.shell("pm clear %s" % packageName).stdout.read().splitlines():
            return "clear user data success "
        else:
            return "make sure package exist"

    def do_reset_current_app(self):
        """
        重置当前应用
        """
        packageName = self.get_current_package_name()
        component = self.get_current_activity()
        self.do_clear_app_data(packageName)
        self.do_start_activity(component)

    def do_start_activity(self, component):
        """
        启动一个Activity
        usage: startActivity(component = "com.android.settinrs/.Settings")
        """
        self.shell("am start -n %s" % component)

    def do_start_webpage(self, url):
        """
        使用系统默认浏览器打开一个网页
        usage: startWebpage("http://www.baidu.com")
        """
        self.shell("am start -a android.intent.action.VIEW -d %s" % url)

    def do_call_phone(self, number):
        """
        启动拨号器拨打电话
        usage: callPhone(10086)
        """
        self.shell("am start -a android.intent.action.CALL -d tel:%s" % str(number))

    def do_send_key_event(self, event_keys):
        """
        发送一个按键事件
        args:
        - event_keys -:
        http://developer.android.com/reference/android/view/KeyEvent.html
        usage: sendKeyEvent(event_keys.HOME)
        """
        self.shell("input keyevent %s" % str(event_keys))
        time.sleep(0.5)

    def do_long_press_key(self, event_keys):
        """
        发送一个按键长按事件,Android 4.4以上
        usage: longPressKey(event_keys.HOME)
        """
        self.shell("input keyevent --longpress %s" % str(event_keys))
        time.sleep(0.5)

    def do_touch(self, e=None, x=None, y=None):
        """
        触摸事件
        usage: touch(e), touch(x=0.5,y=0.5)
        """
        if(e != None):
            x = e[0]
            y = e[1]
        if(0 < x < 1):
            x = x * self.width
        if(0 < y < 1):
            y = y * self.high

        self.shell("input tap %s %s" % (str(x), str(y)))
        time.sleep(0.5)

    def do_touch_by_element(self, element):
        """
        点击元素
        usage: touchByElement(Element().findElementByName(u"计算器"))
        """
        self.shell("input tap %s %s" % (str(element[0]), str(element[1])))
        time.sleep(0.5)

    def do_touch_by_ratio(self, ratioWidth, ratioHigh):
        """
        通过比例发送触摸事件
        args:
        - ratioWidth -:width占比, 0<ratioWidth<1
        - ratioHigh -: high占比, 0<ratioHigh<1
        usage: touchByRatio(0.5, 0.5) 点击屏幕中心位置
        """
        self.shell("input tap %s %s" % (str(ratioWidth * self.getScreenResolution()[0]), str(ratioHigh * self.getScreenResolution()[1])))
        time.sleep(0.5)

    def do_swipe_by_coord(self, start_x, start_y, end_x, end_y, duration=" "):
        """
        滑动事件,Android 4.4以上可选duration(ms)
        usage: swipe(800, 500, 200, 500)
        """
        self.shell("input swipe %s %s %s %s %s" % (str(start_x), str(start_y), str(end_x), str(end_y), str(duration)))
        time.sleep(0.5)

    def do_swipe(self, e1=None, e2=None, start_x=None, start_y=None, end_x=None, end_y=None, duration=" "):
        """
        滑动事件,Android 4.4以上可选duration(ms)
        usage: swipe(e1, e2)
               swipe(e1, end_x=200, end_y=500)
               swipe(start_x=0.5, start_y=0.5, e2)
        """
        if(e1 != None):
            start_x = e1[0]
            start_y = e1[1]
        if(e2 != None):
            end_x = e2[0]
            end_y = e2[1]
        if(0 < start_x < 1):
            start_x = start_x * self.width
        if(0 < start_y < 1):
            start_y = start_y * self.high
        if(0 < end_x < 1):
            end_x = end_x * self.width
        if(0 < end_y < 1):
            end_y = end_y * self.high

        self.shell("input swipe %s %s %s %s %s" % (str(start_x), str(start_y), str(end_x), str(end_y), str(duration)))
        time.sleep(0.5)

    def do_swipe_by_ratio(self, start_ratioWidth, start_ratioHigh, end_ratioWidth, end_ratioHigh, duration=" "):
        """
        通过比例发送滑动事件,Android 4.4以上可选duration(ms)
        usage: swipeByRatio(0.9, 0.5, 0.1, 0.5) 左滑
        """
        self.shell("input swipe %s %s %s %s %s" % (str(start_ratioWidth * self.getScreenResolution()[0]), str(start_ratioHigh * self.getScreenResolution()[1]), \
                                             str(end_ratioWidth * self.getScreenResolution()[0]), str(end_ratioHigh * self.getScreenResolution()[1]), str(duration)))
        time.sleep(0.5)

    def do_swipe_to_left(self):
        """
        左滑屏幕
        """
        self.swipeByRatio(0.8, 0.5, 0.2, 0.5)

    def do_swipe_to_right(self):
        """
        右滑屏幕
        """
        self.swipeByRatio(0.2, 0.5, 0.8, 0.5)

    def do_swipe_to_up(self):
        """
        上滑屏幕
        """
        self.swipeByRatio(0.5, 0.8, 0.5, 0.2)

    def do_swipe_to_down(self):
        """
        下滑屏幕
        """
        self.swipeByRatio(0.5, 0.2, 0.5, 0.8)

    def do_long_press(self, e=None, x=None, y=None):
        """
        长按屏幕的某个坐标位置, Android 4.4
        usage: longPress(e)
               longPress(x=0.5, y=0.5)
        """
        self.swipe(e1=e, e2=e, start_x=x, start_y=y, end_x=x, end_y=y, duration=2000)

    def do_long_press_element(self, e):
        """
       长按元素, Android 4.4
        """
        self.shell("input swipe %s %s %s %s %s" % (str(e[0]), str(e[1]), str(e[0]), str(e[1]), str(2000)))
        time.sleep(0.5)

    def do_long_press_by_ratio(self, ratioWidth, ratioHigh):
        """
        通过比例长按屏幕某个位置, Android.4.4
        usage: longPressByRatio(0.5, 0.5) 长按屏幕中心位置
        """
        self.swipeByRatio(ratioWidth, ratioHigh, ratioWidth, ratioHigh, duration=2000)

    def do_send_text(self, string):
        """
        发送一段文本,只能包含英文字符和空格,多个空格视为一个空格
        usage: sendText("i am unique")
        """
        text = str(string).split(" ")
        out = []
        for i in text:
            if i != "":
                out.append(i)
        length = len(out)
        for i in xrange(length):
            self.shell("input text %s" % out[i])
            if i != length - 1:
                self.sendKeyEvent(EventKeys.SPACE)
        time.sleep(0.5)

    def do_reboot(self):
        """
        重启设备
        """
        self.adb("reboot")

    def do_fastboot(self):
        """
        进入fastboot模式
        """
        self.adb("reboot bootloader")

    def do_kill_process(self, pid):
        """
        杀死应用进程
        args:
        - pid -: 进程pid值
        usage: killProcess(154)
        注:杀死系统应用进程需要root权限
        """
        if self.shell("kill %s" % str(pid)).stdout.read().split(": ")[-1] == "":
            return "kill success"
        else:
            return self.shell("kill %s" % str(pid)).stdout.read().split(": ")[-1]

    def do_quit_app(self, packageName):
        """
        退出app,类似于kill掉进程
        usage: quitApp("com.android.settings")
        """
        self.shell("am force-stop %s" % packageName)

    def do_stop_and_restart_5037(self):
        pid1 = os.popen("netstat -ano | findstr 5037 | findstr  LISTENING").read()
        if pid1 is not None:
            pid = pid1.split()[-1]
        # 下面的命令执行结果,可能因电脑而异,若获取adb.exe时出错,可自行调试!
        # E:\>tasklist /FI "PID eq 10200"
        # Image Name                     PID Session Name        Session#    Mem Usage
        # ========================= ======== ================ =========== ============
        # adb.exe                      10200 Console                    1      6,152 K

        process_name = os.popen('tasklist /FI "PID eq %s"' %pid).read().split()[-6]
        process_path = os.popen('wmic process where name="%s" get executablepath' %process_name).read().split("\r\n")[1]

        # #分割路径,得到进程所在文件夹名
        # name_list = process_path.split("\\")
        # del name_list[-1]
        # directory = "\\".join(name_list)
        # #打开进程所在文件夹
        # os.system("explorer.exe %s" %directory)
        # 杀死该进程
        os.system("taskkill /F /PID %s" %pid)
        os.system("adb start-server")

    def do_input_text(self,text):
        text_list = list(text)
        specific_symbol = set(['&','@','#','$','^','*'])
        for i in range(len(text_list)):
            if text_list[i] in specific_symbol:
                if i-1 < 0:
                    text_list.append(text_list[i])
                    text_list[0] = "\\"
                else:
                    text_list[i-1] = text_list[i-1] + "\\"
        seed = ''.join(text_list)
        self.shell('input text "%s"'%seed)

    def do_capture_window(self):
        self.shell("rm /sdcard/screenshot.png").wait()
        self.shell("/system/bin/screencap -p /sdcard/screenshot.png").wait()
        print ">>>截取屏幕成功,在桌面查看文件。"
        c_time = time.strftime("%Y_%m_%d_%H-%M-%S")
        self.adb('pull /sdcard/screenshot.png T:\\%s.png"'%c_time).wait()

    def get_srceenrecord(self,times, path):
        PATH = lambda p: os.path.abspath(p)
        sdk = string.atoi(self.shell("getprop ro.build.version.sdk").stdout.read())
        try:
            times = string.atoi(times)
        except ValueError, e:
            print ">>>Value error because you enter value is not int type, use default 'times=20s'"
            times = int(20)
        if sdk >= 19:
                self.shell("screenrecord --time-limit %d /data/local/tmp/screenrecord.mp4" % times).wait()
                print ">>>Get Video file..."
                time.sleep(1.5)
                path = PATH(path)
                if not os.path.isdir(path):
                    os.makedirs(path)
                self.adb("pull /data/local/tmp/screenrecord.mp4 %s" % PATH("%s/%s.mp4" % (path, self.timestamp()))).wait()
                self.shell("rm /data/local/tmp/screenrecord.mp4")
                print ">>>ok"
        else:
            print "sdk version is %d, less than 19!" % sdk
            sys.exit(0)

    def get_crash_log(self):
        # 获取app发生crash的时间列表
        time_list = []
        result_list = self.shell("dumpsys dropbox | findstr data_app_crash").stdout.readlines()
        for time in result_list:
            temp_list = time.split(" ")
            temp_time= []
            temp_time.append(temp_list[0])
            temp_time.append(temp_list[1])
            time_list.append(" ".join(temp_time))

        if time_list is None or len(time_list) <= 0:
            print ">>>No crash log to get"
            return None
        log_file = "T://Exception_log_%s.txt" % self.timestamp()
        f = open(log_file, "wb")
        for timel in time_list:
            cash_log = self.shell(timel).stdout.read()
            f.write(cash_log)
        f.close()
        print ">>>check local file"

    def get_permission_list(self, package_name):
        PATH = lambda p: os.path.abspath(p)
        permission_list = []
        result_list = self.shell("dumpsys package %s | findstr android.permission" %package_name).stdout.readlines()
        for permission in result_list:
            permission_list.append(permission.strip())
        pwd = os.path.join(os.getcwd(),"gui_controller\\scriptUtils")
        permission_json_file = file("%s\\permission.json"%pwd)
        file_content = json.load(permission_json_file)["PermissList"]
        name = "_".join(package_name.split("."))
        f = open(PATH("%s\\%s_permission.txt" %(pwd,name)), "w")
        f.write("package: %s\n\n" %package_name)
        for permission in permission_list:
            for permission_dict in file_content:
                if permission == permission_dict["Key"]:
                    f.write(permission_dict["Key"] + ":\n  " + permission_dict["Memo"] + "\n")
        f.close

    def timestamp(self):
        return time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))

    def get_ui_dump_xml(self, xml_path):
        """
        获取当前Activity的控件树
        """
        print xml_path
        PATH = lambda a: os.path.abspath(a)
        if int(self.get_sdk_version()) >= 19:
            self.shell("uiautomator dump --compressed /data/local/tmp/uidump.xml").wait()
        else:
            self.shell("uiautomator dump /data/local/tmp/uidump.xml").wait()
        path = PATH(xml_path)
        if not os.path.isdir(path):
            os.makedirs(path)
        self.adb("pull /data/local/tmp/uidump.xml %s" % PATH(path)).wait()
        self.shell("rm /data/local/tmp/uidump.xml").wait()
        if os.path.exists(os.path.join(path, "uidump.xml")):
            return True
        else:
            return False