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.

307 lines
10 KiB

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