You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
10 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. import datetime
  2. import json
  3. import os
  4. import re
  5. import shutil
  6. import subprocess
  7. import time
  8. import shlex
  9. from android_db import AndroidSQLConn
  10. from load_things import loaded
  11. from decode_parcel import decode_parcel
  12. debug = True
  13. config = loaded.config
  14. keycodes = loaded.keycodes
  15. exe = config.defaults.exe
  16. def merge(src, dst, log=False):
  17. if not os.path.exists(dst):
  18. return False
  19. ok = True
  20. for path, dirs, files in os.walk(src):
  21. relPath = os.path.relpath(path, src)
  22. destPath = os.path.join(dst, relPath)
  23. if not os.path.exists(destPath):
  24. os.makedirs(destPath)
  25. for file in files:
  26. destFile = os.path.join(destPath, file)
  27. if os.path.isfile(destFile):
  28. if log:
  29. print("Skipping existing file: " + os.path.join(relPath, file))
  30. ok = False
  31. continue
  32. srcFile = os.path.join(path, file)
  33. shutil.move(srcFile, destFile)
  34. for path, dirs, files in os.walk(src, False):
  35. if len(files) == 0 and len(dirs) == 0:
  36. os.rmdir(path)
  37. return ok
  38. def _adb(*args, output="shell"):
  39. """Output modes:
  40. "out": return output
  41. "shell": print to shell
  42. "buffered": read line by line"""
  43. args = [exe] + list(args)
  44. if output == "out":
  45. return (
  46. subprocess.check_output(args, shell=False)
  47. .decode()
  48. .replace("\r\n", "\n")
  49. .rstrip()
  50. )
  51. elif output == "shell":
  52. ret = subprocess.call(args, shell=False)
  53. if ret:
  54. raise subprocess.CalledProcessError(ret, args)
  55. elif output == "buffered":
  56. p = subprocess.Popen(args, stdout=subprocess.PIPE)
  57. return p.stdout
  58. def kill_server():
  59. _adb("kill-server")
  60. def start_server():
  61. _adb("start-server")
  62. def get_info():
  63. start_server()
  64. thing = _adb("devices", "-l", output="out")
  65. formed = list(filter(bool, thing.split("\n")))[1:]
  66. main = {}
  67. for device in formed:
  68. categories = re.split(" +", device)
  69. device_dict = {"serial": categories[0], "mode": categories[1]}
  70. device_dict.update(dict(category.split(":") for category in categories[2:]))
  71. main[categories[0]] = device_dict
  72. return main
  73. class ADBWrapper:
  74. root_mode = False
  75. def connect(ip, port=5555):
  76. if not re.match(r"(\d{1,3}\.){3}\d{1,3}", ip):
  77. raise TypeError("Invalid ip")
  78. if not all(int(n) <= 255 and int(n) >= 0 for n in ip.split(".")):
  79. raise TypeError("Invalid ip")
  80. if not (port >= 0 and port <= 2 ** 16 - 1):
  81. raise TyperError("Port must be in the range 0-65536")
  82. id = "{}:{}".format(ip, port)
  83. _adb("connect", "{}:{}".format(ip, port))
  84. dev = Device(id)
  85. dev.tcip = True
  86. return dev
  87. def disconnect(self):
  88. if self.tcip:
  89. _adb("disconnect", self.serial)
  90. def db_connect(self, filepath):
  91. return AndroidSQLConn(self, filepath)
  92. def prim_device():
  93. cont = True
  94. while cont:
  95. try:
  96. d = Device()
  97. cont = False
  98. except IndexError:
  99. time.sleep(1)
  100. return d
  101. def __init__(self, serial=None):
  102. self.tcip = False
  103. if serial:
  104. self.serial = serial
  105. info = get_info()[serial]
  106. else:
  107. serial, info = list(get_info().items())[0]
  108. self.__dict__.update(info)
  109. def adb(self, *args, output="shell"):
  110. args = ["-s", self.serial] + list(args)
  111. return _adb(*args, output=output)
  112. def shell(self, *args, output="shell"):
  113. args = ("shell",) + args
  114. return self.adb(*args, output=output)
  115. def sudo(self, *args, output="shell"):
  116. if self.mode == "recovery" or self.root_mode:
  117. return self.shell(*args, output=output)
  118. else:
  119. return self.shell("su", "--", "--", *args, output=output)
  120. @classmethod
  121. def root(cls):
  122. cls.root_mode = True
  123. _adb("root")
  124. @classmethod
  125. def unroot(cls):
  126. cls.root_mode = False
  127. _adb("unroot")
  128. def reboot(self, mode=None):
  129. if mode:
  130. if mode == "soft":
  131. if self.mode != "recovery":
  132. pid = self.shell("pidof", "zygote", output="out")
  133. return self.sudo("kill", pid, output="shell")
  134. else:
  135. return self.reboot()
  136. else:
  137. self.adb("reboot", mode)
  138. else:
  139. self.adb("reboot")
  140. while True:
  141. infos = get_info()
  142. if len(infos) > 0:
  143. self.__dict__.update(infos[self.serial])
  144. break
  145. time.sleep(1)
  146. class FSActionWrapper(ADBWrapper):
  147. def stat(self, file):
  148. """\
  149. %a Access bits (octal) |%A Access bits (flags)|%b Size/512
  150. %B Bytes per %b (512) |%d Device ID (dec) |%D Device ID (hex)
  151. %f All mode bits (hex) |%F File type |%g Group ID
  152. %G Group name |%h Hard links |%i Inode
  153. %m Mount point |%n Filename |%N Long filename
  154. %o I/O block size |%s Size (bytes) |%t Devtype major (hex)
  155. %T Devtype minor (hex) |%u User ID |%U User name
  156. %x Access time |%X Access unix time |%y File write time
  157. %Y File write unix time|%z Dir change time |%Z Dir change unix time
  158. The valid format escape sequences for filesystems:
  159. %a Available blocks |%b Total blocks |%c Total inodes
  160. %d Free inodes |%f Free blocks |%i File system ID
  161. %l Max filename length |%n File name |%s Fragment size
  162. %S Best transfer size |%t FS type (hex) |%T FS type (driver name)"""
  163. command = 'stat -c "%A;%F;%U;%G" {};echo $?'.format(file)
  164. res = self.sudo(command, output="out")
  165. output, res = res.split("\n")
  166. if res == "0":
  167. return output.split(";")
  168. def exists(self, file):
  169. return self.stat(file) is not None
  170. def isfile(self, file):
  171. return self.stat(file)[1] == "file"
  172. def isdir(self, file):
  173. return self.stat(file)[1] == "directory"
  174. def islink(self, file):
  175. return self.stat(file)[1] == "symbolic link"
  176. def delete(self, path):
  177. return self.sudo("rm", "-rf", path, output="out")
  178. def copy(self, remote, local, del_duplicates=True, ignore_error=True):
  179. remote_stat = self.stat(remote)
  180. if remote_stat is not None:
  181. if remote_stat[1] == "directory" and not remote.endswith("/"):
  182. remote += "/"
  183. merge_flag = False
  184. if os.path.exists(local):
  185. last = os.path.split(local)[-1]
  186. real_dir = local
  187. local = os.path.join(config["local"]["temp"], last)
  188. merge_flag = True
  189. try:
  190. self.adb("pull", "-a", remote, local)
  191. except subprocess.CalledProcessError as e:
  192. if ignore_error:
  193. pass
  194. else:
  195. raise e
  196. if merge_flag:
  197. merge(local, real_dir)
  198. if os.path.exists(local) and del_duplicates:
  199. shutil.rmtree(local)
  200. else:
  201. print("File not found: {}".format(remote))
  202. def move(self, remote, local, del_duplicates=True, ignore_error=False):
  203. if self.exists(remote):
  204. self.copy(
  205. remote, local, del_duplicates=del_duplicates, ignore_error=ignore_error
  206. )
  207. self.delete(remote)
  208. else:
  209. print("File not found: {}".format(remote))
  210. def push(self, local, remote):
  211. self.adb("push", local, remote)
  212. class Input(ADBWrapper):
  213. def send_keycode(self, code):
  214. try:
  215. keycode = keycodes[code]
  216. except KeyError:
  217. keycode = str(code)
  218. self.shell("input", "keyevent", keycode)
  219. def unlock_phone(self, password):
  220. if self.mode == "recovery":
  221. return
  222. if not decode_parcel(
  223. self.shell("service", "call", "power", "12", output="out"), "int"
  224. ):
  225. self.send_keycode("power")
  226. if decode_parcel(
  227. self.shell("service", "call", "trust", "7", output="out"), "int"
  228. ):
  229. self.send_keycode("space")
  230. self.shell("input", "text", str(password))
  231. self.send_keycode("enter")
  232. class TWRP(FSActionWrapper):
  233. def backup(self, *partitions, name=None, backupdir=None):
  234. if self.mode != "recovery":
  235. self.reboot("recovery")
  236. if backupdir is None:
  237. backupdir = config["local"]["twrp"]
  238. else:
  239. backupdir = backupdir
  240. options_dict = {
  241. "system": "S",
  242. "data": "D",
  243. "cache": "C",
  244. "recovery": "R",
  245. "spec_part_1": "1",
  246. "spec_part_2": "2",
  247. "spec_part_3": "3",
  248. "boot": "B",
  249. "as": "A",
  250. }
  251. options = "".join(options_dict[option] for option in partitions)
  252. if not name:
  253. name = "backup_" + datetime.datetime.today().strftime(
  254. defaults["date_format"]
  255. )
  256. filename = os.path.join(backupdir, name)
  257. self.shell("twrp", "backup", options, name)
  258. phone_dir = "/data/media/0/TWRP/BACKUPS/{serial}/{name}".format(
  259. serial=self.serial, name=name
  260. )
  261. self.move(phone_dir, filename)
  262. def wipe(self, partition):
  263. if self.mode != "recovery":
  264. self.reboot("recovery")
  265. self.shell("twrp", "wipe", partition)
  266. def install(self, name):
  267. if self.mode != "recovery":
  268. self.reboot("recovery")
  269. if os.path.exists(name):
  270. local_name = name
  271. name = os.path.split(name)[-1]
  272. update_path = "{}/{}".format(config["remote"]["updates"], name)
  273. if not self.exists(config["remote"]["updates"]):
  274. self.sudo("mkdir", config["remote"]["updates"])
  275. if not self.exists(update_path):
  276. self.push(local_name, config["remote"]["updates"])
  277. else:
  278. update_path = "{}/{}".format(config["remote"]["updates"], name)
  279. self.shell("twrp", "install", update_path)
  280. class Device(TWRP, Input):
  281. pass
  282. if __name__ == "__main__" and debug:
  283. d = Device.prim_device()