[Python]Android SDK adb + PyQT5写一个拉取Android设备文件的应用

场景:

        移动设备对移动存储媒介读取功能已经受限,但是Android SDK的权限依然开放且可供开发

        例如:测试人员对软件功能点录屏截屏的证据无法直接通过数据线传输到电脑并上传到项目管理平台上。

        声明:不可用于违反公司企业信息安全的操作,请确认操作是否合规,保证信息安全。

前提条件:

  • Android SDK已经下载安装且配置到环境变量当中
  • Python环境已安装PyQT5
import datetime
import subprocess
import sys

from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtNetwork import QLocalSocket, QLocalServer
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QTextEdit


def is_directory(path):
    result = subprocess.run(['adb', 'shell', f'if [ -d "{path}" ]; '
                                             f'then echo "directory"; else echo "file"; fi'], stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    return result.stdout.decode('utf-8').strip() == 'directory'


class ADBPullApp(QWidget):
    finished = pyqtSignal(bool, str)

    def __init__(self):
        super().__init__()
        self.info_label = None
        self.worker = None
        self.file_name_input = None
        self.files_display = None
        self.refresh_button = None
        self.folder_path_input = None
        self.device_status_label = None
        self.layout = None
        self.pull_button = None
        self.initUI()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.check_device_connection)
        # 每2秒检测一次设备连接
        self.timer.start(2000)

    def initUI(self):
        self.layout = QVBoxLayout()

        self.device_status_label = QLabel('Checking device connection...')
        self.layout.addWidget(self.device_status_label)

        self.folder_path_input = QLineEdit(self)
        self.folder_path_input.setText('/sdcard/DCIM/')
        self.layout.addWidget(self.folder_path_input)

        self.refresh_button = QPushButton('Refresh', self)
        self.refresh_button.clicked.connect(self.refresh_files)
        self.layout.addWidget(self.refresh_button)

        self.files_display = QTextEdit(self)
        self.files_display.setReadOnly(True)
        self.layout.addWidget(self.files_display)

        self.file_name_input = QLineEdit(self)
        self.file_name_input.setPlaceholderText('Enter the file name in the folder')
        self.layout.addWidget(self.file_name_input)

        self.pull_button = QPushButton('Pull File', self)
        self.pull_button.setEnabled(False)
        self.pull_button.clicked.connect(self.pull_file)
        self.layout.addWidget(self.pull_button)

        self.info_label = QLabel('1.You must have adb tool in your device and already in environment!!!\n'
                                 '2.The refresh list only show today\'s file.\n'
                                 '3.Open developers model and turn on th USB debug of your android device.\n'
                                 '4.Only can pull file from android device.')
        self.layout.addWidget(self.info_label)

        self.setLayout(self.layout)
        self.setWindowTitle('PW File Puller')
        self.show()

    def check_device_connection(self):
        result = subprocess.run(['adb', 'devices'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        devices_output = result.stdout.decode('utf-8')

        if "device" in devices_output.split():
            self.device_status_label.setText('Device connected.')
            self.pull_button.setEnabled(True)
        else:
            self.device_status_label.setText('No device connected.')
            self.pull_button.setEnabled(False)

    def refresh_files(self):
        folder_path = self.folder_path_input.text()
        today = datetime.datetime.now().strftime('%Y-%m-%d')
        cmd = f'adb shell "find {folder_path} -type f -newermt {today} 2>/dev/null"'
        result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        files_output = result.stdout.decode('utf-8')
        self.files_display.setPlainText(files_output)

    def pull_file(self):
        folder_path = self.folder_path_input.text()
        file_name = self.file_name_input.text()
        if file_name:
            full_path = f"{folder_path}/{file_name}"
        else:
            full_path = folder_path

        if is_directory(full_path):
            reply = QMessageBox.question(self, 'Confirm', 'The path is a directory. Do you want to '
                                                          'pull the entire directory?',
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.No:
                return

        self.pull_button.setEnabled(False)

        result = subprocess.run(['adb', 'pull', full_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if result.returncode == 0:
            QMessageBox.information(self, 'Success', 'File pulled successfully to current directory.\n'
                                                     'Please wait some seconds if it\'s not appear.')
        elif full_path is None:
            QMessageBox.warning(self, 'Warn', 'The path is empty')
        else:
            QMessageBox.critical(self, 'Error', f'Failed to pull file: {result.stderr.decode("utf-8")}')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    try:
        serverName = 'PWPULLFILE'
        socket = QLocalSocket()
        # 建立socket,防止应用多开
        socket.connectToServer(serverName)  
        if socket.waitForConnected(500):
            app.quit()
        else:
            localServer = QLocalServer()
            localServer.listen(serverName)
            ex = ADBPullApp()
    finally:
        sys.exit(app.exec_())

效果:

109a9855627a448eb36ac09338bbcb7b.png

功能点:

  1.  刷新递归查询当前路径及子目录下所有今天创建的文件,且显示在列表中供用户复制粘贴实现特定拉取
  2.  如果只输入了文件夹路径,则会拉取整个文件夹到当前所在路径中
  3.  如果输入了文件夹下的特定文件则只拉取文件
  4.  随插随拔,自动检测设备是否连接

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值