Skip to content

Commit 014dd4f

Browse files
author
codefl0w
committed
V4.1.0 Release: device selection menu & APK install flags
1 parent 3e94b98 commit 014dd4f

16 files changed

Lines changed: 1527 additions & 840 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ The changelog only includes what's new. To read feature descriptions, see [Featu
44
For the old changelog, see [README_OLD](https://github.com/codefl0w/QuickADB/blob/main/README_OLD.md).
55

66

7+
# [4.1.0] - 26.03.2026
8+
9+
### Added
10+
- Device selection support
11+
- Centralized tool path management
12+
- Install APK flags
13+
14+
### Changed
15+
- QuickADB: Re-labeled "extract logs" to "export logs"
16+
- QuickADB: Changed contact method from email to website
17+
- GSI Flasher: Removed support for compressed GSI images (.img.gz, .img.xz)
18+
- Wireless ADB: fixed freezing on 15-second timeout
19+
20+
21+
### Improved
22+
- File Explorer: Refactored code to eliminate duplication and redundancy
723

824

925
# [4.0.2] - 14.03.2026

main/adbfunc.py

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@
1111
import sys
1212
import os
1313

14-
from util.resource import get_root_dir, resource_path, resolve_platform_tool
14+
from util.resource import get_root_dir, resource_path
15+
from util.toolpaths import ToolPaths
1516
root_dir = get_root_dir()
1617
if root_dir not in sys.path:
1718
sys.path.insert(0, root_dir)
1819

1920
import subprocess
2021
import math
2122
from PyQt6.QtWidgets import (QFileDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel,
22-
QLineEdit, QPushButton, QMessageBox, QTextEdit)
23+
QLineEdit, QPushButton, QMessageBox, QTextEdit, QCheckBox)
2324
from PyQt6.QtCore import Qt, QThread, pyqtSignal
2425
import threading
2526

@@ -100,7 +101,7 @@ class DeviceInfoWorker(QThread):
100101
def __init__(self, platform_tools_path):
101102
super().__init__()
102103
self.platform_tools_path = platform_tools_path
103-
self.adb_path = resolve_platform_tool(platform_tools_path, "adb")
104+
self.adb_path = ToolPaths.instance().adb
104105

105106
def run(self):
106107
commands = self._get_device_commands()
@@ -141,7 +142,9 @@ def run(self):
141142
self.info_ready.emit("\n".join(results))
142143

143144
def _get_device_commands(self):
144-
adb_cmd = f'"{self.adb_path}"'
145+
from util.devicemanager import DeviceManager
146+
serial_flag = DeviceManager.instance().serial_flag()
147+
adb_cmd = f'"{self.adb_path}" {serial_flag}'
145148
return {
146149
"Fingerprint": f"{adb_cmd} shell getprop ro.build.fingerprint",
147150
"Board": f"{adb_cmd} shell getprop ro.product.board",
@@ -227,10 +230,90 @@ def _start_info_worker(self, platform_tools_path):
227230

228231
# [Legacy WirelessADBDialog removed - now using modules.wirelessadb]
229232

233+
class InstallFlagsDialog(QDialog):
234+
def __init__(self, current_flags, parent=None):
235+
super().__init__(parent)
236+
self.current_flags = current_flags.copy() if current_flags else {}
237+
self.result_flags = self.current_flags.copy()
238+
self._setup_ui()
239+
240+
def _setup_ui(self):
241+
self.setWindowTitle("Install Flags")
242+
self.setMinimumWidth(450)
243+
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
244+
self.setModal(True)
245+
246+
layout = QVBoxLayout()
247+
248+
self.checkboxes = {}
249+
flags_info = [
250+
("-r", "replace existing application"),
251+
("-t", "allow test packages"),
252+
("-d", "allow version code downgrade (debuggable packages only)"),
253+
("-p", "partial application install (install-multiple only)"),
254+
("-g", "grant all runtime permissions"),
255+
("--instant", "cause the app to be installed as an ephemeral install app"),
256+
("--no-streaming", "always push APK to device and invoke Package Manager as separate steps"),
257+
("--streaming", "force streaming APK directly into Package Manager")
258+
]
259+
260+
for flag, desc in flags_info:
261+
cb = QCheckBox(f"{flag}: {desc}")
262+
if self.current_flags.get(flag, False):
263+
cb.setChecked(True)
264+
self.checkboxes[flag] = cb
265+
layout.addWidget(cb)
266+
267+
# ABI flag
268+
abi_layout = QHBoxLayout()
269+
self.abi_cb = QCheckBox("--abi: override platform's default ABI")
270+
self.abi_cb.setChecked("--abi" in self.current_flags)
271+
272+
self.abi_entry = QLineEdit()
273+
self.abi_entry.setPlaceholderText("e.g. arm64-v8a")
274+
self.abi_entry.setEnabled(self.abi_cb.isChecked())
275+
if "--abi" in self.current_flags:
276+
self.abi_entry.setText(self.current_flags["--abi"])
277+
278+
self.abi_cb.toggled.connect(self.abi_entry.setEnabled)
279+
280+
abi_layout.addWidget(self.abi_cb)
281+
abi_layout.addWidget(self.abi_entry)
282+
layout.addLayout(abi_layout)
283+
284+
# Buttons
285+
btn_layout = QHBoxLayout()
286+
btn_layout.addStretch()
287+
cancel_btn = QPushButton("Cancel")
288+
cancel_btn.clicked.connect(self.reject)
289+
apply_btn = QPushButton("Apply")
290+
apply_btn.clicked.connect(self._apply)
291+
btn_layout.addWidget(cancel_btn)
292+
btn_layout.addWidget(apply_btn)
293+
layout.addLayout(btn_layout)
294+
self.setLayout(layout)
295+
296+
def _apply(self):
297+
self.result_flags.clear()
298+
for flag, cb in self.checkboxes.items():
299+
if cb.isChecked():
300+
self.result_flags[flag] = True
301+
302+
if self.abi_cb.isChecked() and self.abi_entry.text().strip():
303+
self.result_flags["--abi"] = self.abi_entry.text().strip()
304+
305+
if self.result_flags.get("--streaming", False) and self.result_flags.get("--no-streaming", False):
306+
QMessageBox.warning(self, "Conflict", "You cannot select both --streaming and --no-streaming.")
307+
return
308+
309+
self.accept()
310+
311+
230312
class InstallAPKDialog(QDialog):
231313
def __init__(self, parent=None):
232314
super().__init__(parent)
233315
self.parent_app = parent
316+
self.install_flags = {}
234317
self._setup_ui()
235318

236319
def _setup_ui(self):
@@ -256,14 +339,27 @@ def _setup_ui(self):
256339

257340
layout.addLayout(path_layout)
258341

259-
# Install button
342+
# Setup custom buttons
343+
action_layout = QHBoxLayout()
344+
345+
self.flags_btn = QPushButton("Flags")
346+
self.flags_btn.clicked.connect(self._open_flags)
347+
260348
install_btn = QPushButton("Install APK")
261349
install_btn.clicked.connect(self._install_apk)
262350
install_btn.setDefault(True)
263-
layout.addWidget(install_btn)
351+
352+
action_layout.addWidget(self.flags_btn)
353+
action_layout.addWidget(install_btn)
354+
layout.addLayout(action_layout)
264355

265356
self.setLayout(layout)
266357

358+
def _open_flags(self):
359+
dialog = InstallFlagsDialog(self.install_flags, self)
360+
if dialog.exec() == QDialog.DialogCode.Accepted:
361+
self.install_flags = dialog.result_flags
362+
267363
def _browse_apk(self):
268364
file_path, _ = QFileDialog.getOpenFileName(
269365
self, "Select APK File", "", "APK Files (*.apk);;All Files (*)"
@@ -282,8 +378,18 @@ def _install_apk(self):
282378
return
283379

284380
if hasattr(self.parent_app, 'run_command_async'):
381+
flag_str = ""
382+
for k, v in self.install_flags.items():
383+
if k == "--abi":
384+
flag_str += f" --abi {v}"
385+
else:
386+
flag_str += f" {k}"
387+
flag_str = flag_str.strip()
388+
389+
cmd = f'adb install {flag_str} "{apk_path}"' if flag_str else f'adb install "{apk_path}"'
390+
285391
self.parent_app.run_command_async(
286-
f'adb install "{apk_path}"',
392+
cmd,
287393
f"Installing {os.path.basename(apk_path)}",
288394
"ADB"
289395
)
@@ -518,13 +624,9 @@ def sideload_file(self):
518624

519625

520626
def show_wireless_adb_ui(self):
521-
import os
522627
from modules.wirelessadb import WirelessADBDialog
523628
# Show wireless ADB connection dialog (new QR pairing module)
524-
# Passed parent and platform_tools_path dynamically since parent represents quickadb.py
525-
adb_exe = "adb.exe" if os.name == 'nt' else "adb"
526-
adb_path = os.path.join(self.platform_tools_path, adb_exe)
527-
dialog = WirelessADBDialog(self, adb_path)
629+
dialog = WirelessADBDialog(self, ToolPaths.instance().adb)
528630
dialog.exec()
529631

530632

0 commit comments

Comments
 (0)