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.

278 lines
8.9 KiB

import datetime
import json
import os
import re
import shutil
import subprocess
import time
import shlex
from android_db import AndroidSQLConn
from load_things import loaded
from decode_parcel import decode_parcel
debug = True
config = loaded.config
keycodes = loaded.keycodes
exe = config.defaults.exe
exists = '''if [ -e "{file}" ]; then
if [ -d "{file}" ]; then
echo "directory"
elif [ -f "{file}" ]; then
echo "file"
else
echo "error"
fi
else
echo "na"
fi'''
def merge(src, dst,log = False):
if not os.path.exists(dst):
return False
ok = True
for path, dirs, files in os.walk(src):
relPath = os.path.relpath(path, src)
destPath = os.path.join(dst, relPath)
if not os.path.exists(destPath):
os.makedirs(destPath)
for file in files:
destFile = os.path.join(destPath, file)
if os.path.isfile(destFile):
if log:
print("Skipping existing file: " + os.path.join(relPath, file))
ok = False
continue
srcFile = os.path.join(path, file)
shutil.move(srcFile, destFile)
for path, dirs, files in os.walk(src, False):
if len(files) == 0 and len(dirs) == 0:
os.rmdir(path)
return ok
def _adb(*args,output = "shell"):
'''Output modes:
"out": return output
"shell": print to shell
"buffered": read line by line'''
args = [exe] + list(args)
if output == "out":
return subprocess.check_output(args,shell = False).decode().rstrip('\r\n')
elif output == "shell":
ret = subprocess.call(args,shell = False)
if ret:
raise subprocess.CalledProcessError(ret,args)
elif output == "buffered":
p = subprocess.Popen(args,stdout = subprocess.PIPE)
return p.stdout
def kill_server():
_adb('kill-server')
def start_server():
_adb('start-server')
def get_info():
start_server()
thing = _adb("devices","-l",output="out")
formed = list(filter(bool,thing.split("\r\n")))[1:]
main = {}
for device in formed:
categories = re.split(" +",device)
device_dict = {
"serial":categories[0],
"mode":categories[1]
}
device_dict.update(dict(category.split(":") for category in categories[2:]))
main[categories[0]] = device_dict
return main
class Device:
root_mode = False
def connect(ip,port=5555):
if not re.match(r'(\d{1,3}\.){3}\d{1,3}',ip):
raise TypeError("Invalid ip")
if not all(int(n) <= 255 and int(n) >= 0 for n in ip.split('.')):
raise TypeError("Invalid ip")
if not (port >= 0 and port <= 2**16-1):
raise TyperError("Port must be in the range 0-65536")
id = '{}:{}'.format(ip,port)
_adb('connect','{}:{}'.format(ip,port))
dev = Device(id)
dev.tcip = True
return dev
def disconnect(self):
if self.tcip:
_adb('disconnect',self.serial)
def db_connect(self,filepath):
return AndroidSQLConn(self,filepath)
def prim_device():
cont = True
while cont:
try:
d = Device()
cont = False
except IndexError:
time.sleep(1)
return d
def __init__(self,serial=None):
self.tcip = False
if serial:
self.serial = serial
info = get_info()[serial]
else:
serial,info = list(get_info().items())[0]
self.__dict__.update(info)
def adb(self,*args,output="shell"):
args = ['-s',self.serial]+ list(args)
return _adb(*args,output = output)
def shell(self,*args,output="shell"):
args = ('shell',)+args
return self.adb(*args,output=output)
def sudo(self,*args,output="shell"):
if self.mode == 'recovery' or self.root_mode:
return self.shell(*args,output=output)
else:
return self.shell('su','--','--',*args,output=output)
@classmethod
def root(cls):
cls.root_mode = True
_adb('root')
@classmethod
def unroot(cls):
cls.root_mode = False
_adb('unroot')
def type(self,file):
e = exists.format(file = file)
res = self.sudo(e,output="out")
return res
def exists(self,file):
return self.type(file) != "na"
def isfile(self,file):
return self.type(file) == 'file'
def isdir(self,file):
return self.type(file) == 'directory'
def delete(self,path):
return self.sudo("rm","-rf",path,output="out")
def copy(self,remote,local,del_duplicates = True,ignore_error=True):
remote_type = self.type(remote)
if remote_type != "na":
if remote_type == "directory" and not remote.endswith('/'):
remote += '/'
merge_flag = False
if os.path.exists(local):
last = os.path.split(local)[-1]
real_dir = local
local = os.path.join(config['local']['temp'],last)
merge_flag = True
try:
self.adb("pull","-a",remote,local)
except subprocess.CalledProcessError as e:
if ignore_error:
pass
else:
raise e
if merge_flag:
merge(local,real_dir)
if os.path.exists(local) and del_duplicates:
shutil.rmtree(local)
else:
print("File not found: {}".format(remote))
def move(self,remote,local,del_duplicates = True,ignore_error=False):
if self.exists(remote):
self.copy(remote,local,del_duplicates = del_duplicates,ignore_error=ignore_error)
self.delete(remote)
else:
print("File not found: {}".format(remote))
def push(self,local,remote):
self.adb('push',local,remote)
def reboot(self,mode = None):
if mode:
if mode == "soft":
if self.mode != 'recovery':
pid = self.shell("pidof","zygote",output="out")
return self.sudo("kill",pid,output="shell")
else:
return self.reboot()
else:
self.adb("reboot",mode)
else:
self.adb("reboot")
while True:
infos = get_info()
if len(infos) > 0:
self.__dict__.update(infos[self.serial])
break
time.sleep(1)
def send_keycode(self,code):
try:
keycode = keycodes[code]
except KeyError:
keycode = str(code)
self.shell("input","keyevent",keycode)
def unlock_phone(self,password):
if self.mode != 'recovery':
if not decode_parcel(self.shell('service','call', 'power','12',output="out"),'int'):
self.send_keycode('power')
if decode_parcel(self.shell('service','call','trust','7',output="out"),'int'):
self.send_keycode('space')
self.shell("input","text",str(password))
self.send_keycode('enter')
def backup(self,*partitions,name = None,backupdir = None):
if self.mode != 'recovery':
self.reboot('recovery')
if backupdir is None:
backupdir = config['local']['twrp']
else:
backupdir = backupdir
options_dict = {
"system": "S",
"data": "D",
"cache": "C",
"recovery": "R",
"spec_part_1": "1",
"spec_part_2": "2",
"spec_part_3": "3",
"boot": "B",
"as": "A"
}
options = "".join(options_dict[option] for option in partitions)
if not name:
name = "backup_"+datetime.datetime.today().strftime(defaults['date_format'])
filename = os.path.join(backupdir,name)
self.shell("twrp","backup",options,name)
phone_dir = "/data/media/0/TWRP/BACKUPS/{serial}/{name}".format(serial = self.serial,name = name)
self.move(phone_dir,filename)
def wipe(self,partition):
if self.mode != 'recovery':
self.reboot('recovery')
self.shell("twrp","wipe",partition)
def install(self,name):
if self.mode != 'recovery':
self.reboot('recovery')
if os.path.exists(name):
local_name = name
name = os.path.split(name)[-1]
update_path = '{}/{}'.format(config['remote']['updates'],name)
if not self.exists(config['remote']['updates']):
self.sudo('mkdir',config['remote']['updates'])
if not self.exists(update_path):
self.push(local_name,config['remote']['updates'])
else:
update_path = '{}/{}'.format(config['remote']['updates'],name)
self.shell("twrp","install",update_path)
if __name__ == "__main__" and debug:
d = Device.prim_device()