From 0a6000435978cdcb421751a76be2367eaf7cf134 Mon Sep 17 00:00:00 2001 From: Raphael Roberts Date: Sat, 13 Oct 2018 21:00:58 -0500 Subject: [PATCH] fixed su, added db interface root/unroot method, ensures adb is running before gettting first device --- adb.py | 93 ++++++++++++++++++++++++++++----------------------- android_db.py | 46 +++++++++++++++++++++++++ output | 1 + sync_clip.py | 25 ++++++++++++++ test.py | 6 ++++ 5 files changed, 130 insertions(+), 41 deletions(-) create mode 100644 android_db.py create mode 100644 output create mode 100644 sync_clip.py create mode 100644 test.py diff --git a/adb.py b/adb.py index 652aea9..99b4be8 100644 --- a/adb.py +++ b/adb.py @@ -5,10 +5,11 @@ 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 - defaults = loaded.defaults keycodes = loaded.keycodes exe = defaults.exe @@ -23,6 +24,7 @@ exists = '''if [ -e "{file}" ]; then else echo "na" fi''' + def merge(src, dst,log = False): if not os.path.exists(dst): return False @@ -46,17 +48,29 @@ def merge(src, dst,log = False): os.rmdir(path) return ok -def _adb(*args,out = False): +def _adb(*args,output = "shell"): + '''Output modes: + "out": return output + "shell": print to shell + "buffered": read line by line''' args = [exe] + list(args) - if out: + if output == "out": return subprocess.check_output(args,shell = False).decode().rstrip('\r\n') - else: + 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(): - thing = _adb("devices","-l",out = True) + start_server() + thing = _adb("devices","-l",output="out") formed = list(filter(bool,thing.split("\r\n")))[1:] main = {} for device in formed: @@ -65,12 +79,11 @@ def get_info(): "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") @@ -87,8 +100,8 @@ class Device: 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: @@ -108,37 +121,43 @@ class Device: serial,info = list(get_info().items())[0] self.__dict__.update(info) - - - def adb(self,*args,out = False): + def adb(self,*args,output="shell"): args = ['-s',self.serial]+ list(args) - return _adb(*args,out = out) - def shell(self,*args,out=False): - args = ('shell',)+args - return self.adb(*args,out=out) - def sudo(self,*args,out = False): - if self.mode == 'recovery': - return self.shell(*args,out=out) - else: - args = '"{}"'.format(' '.join(args).replace('"','\\"')) - return self.shell('su','-c',args,out = out) - + 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,out=True) + 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,out=True) + return self.sudo("rm","-rf",path,output="out") def copy(self,remote,local,del_duplicates = True,ignore_error=True): remote_type = self.type(remote) @@ -175,17 +194,14 @@ class Device: 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",out = True) - return self.sudo("kill",pid,out=False) + pid = self.shell("pidof","zygote",output="out") + return self.sudo("kill",pid,output="shell") else: return self.reboot() - else: self.adb("reboot",mode) else: @@ -206,15 +222,13 @@ class Device: def unlock_phone(self,password): if self.mode != 'recovery': - if not decode_parcel(self.shell('service','call', 'power','12',out=True),'int'): + 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',out=True),'int'): + 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): if self.mode != 'recovery': self.reboot('recovery') @@ -231,10 +245,8 @@ class Device: "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) @@ -252,13 +264,12 @@ class Device: local_name = name name = os.path.split(name)[-1] update_path = '{}/{}'.format(defaults['remote']['updates'],name) + if not self.exists(defaults['remote']['updates']): + self.sudo('mkdir',defaults['remote']['updates']) if not self.exists(update_path): self.push(local_name,defaults['remote']['updates']) - else: update_path = '{}/{}'.format(defaults['remote']['updates'],name) self.shell("twrp","install",update_path) - - if __name__ == "__main__" and debug: - d = Device.prim_device() + d = Device.prim_device() \ No newline at end of file diff --git a/android_db.py b/android_db.py new file mode 100644 index 0000000..9df7abd --- /dev/null +++ b/android_db.py @@ -0,0 +1,46 @@ +import shlex +import re +import csv +import sys +csv.field_size_limit(2**31-1) +param_str = re.compile(r'(?<=\()[ ,\?]+(?=\))') +validator = re.compile(r'^\?(?: *\, *\?)*$') +param_sep = re.compile(r' *\, *') +class AndroidSQLConn: + def __init__(self,device,filepath,use_root=True): + self.device = device + self.filepath = filepath + self.root = use_root + + def _quote_param_(param): + if isinstance(param,str): + return "'{}'".format(param.replace("'","''")) + elif isinstance(param,bool): + return '1' if param else '0' + elif param is None: + return 'NULL' + else: + return str(param) + def _sub_params_(SQL,params): + p_string = param_str.search(SQL).group(0) + if not validator.match(p_string): + raise ValueError('Invalid substitution') + + n_params = len(param_sep.split(p_string)) + if len(params) < n_params: + raise ValueError('Not enough parameters supplied') + new_str = ','.join(map( + AndroidSQLConn._quote_param_,params[:n_params] + )) + return param_str.sub(new_str,SQL,1) + def execute(self,SQL,params = None): + if params: + SQL = AndroidSQLConn._sub_params_(SQL,params) + SQL = shlex.quote(SQL) + if self.root: + shell = self.device.sudo + else: + shell = self.device.shell + out = shell('sqlite3','-header','-csv',shlex.quote(self.filepath),SQL,output="out") + if out: + return csv.DictReader(out.splitlines()) diff --git a/output b/output new file mode 100644 index 0000000..36487b9 --- /dev/null +++ b/output @@ -0,0 +1 @@ +'X:\\Users\\Ralphie\\Google Drive\\Python\\lib\\shlex.py' \ No newline at end of file diff --git a/sync_clip.py b/sync_clip.py new file mode 100644 index 0000000..d217381 --- /dev/null +++ b/sync_clip.py @@ -0,0 +1,25 @@ +import pyperclip +import time +import re +def sync_clipboard(dev): + db_file = '/data/data/com.catchingnow.tinyclipboardmanager/databases/clippingnow.db' + try: + pid = dev.shell('pidof','com.catchingnow.tinyclipboardmanager',output="out") + dev.sudo('kill',pid,output='shell') + except: + pass + t = time.time() + text = pyperclip.paste() + conn = dev.db_connect(db_file) + # text = re.sub(r'([\{\}])',lambda match: match.group(1) * 2,text) + # sql = 'INSERT INTO cliphistory values ({},\'{}\',0)'.format(int(1000*t),text) + # sql = '"{}"'.format(sql) + # print(sql) + # dev.sudo('sqlite3','-csv',db_file,sql) + out = conn.execute("INSERT INTO cliphistory VALUES (?,?,?);",[int(1000*t),text,False]) + # print(list(out)) + dev.shell('am', 'start', '-n', 'com.catchingnow.tinyclipboardmanager/.activity.ActivityMain') +if __name__ == "__main__": + import adb + d = adb.Device.prim_device() + sync_clipboard(d) \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..75cc5dc --- /dev/null +++ b/test.py @@ -0,0 +1,6 @@ +from adb import Device +d = Device.prim_device() +import time +d.root() +conn = d.db_connect('/data/data/com.catchingnow.tinyclipboardmanager/databases/clippingnow.db') +out = conn.execute('INSERT INTO cliphistory (date,history,star) values (?,?,?)',[int(time.time()*1000),"test_test",False]) \ No newline at end of file