From a4d13d47241cac0eb8b9b4714f5ea1d9345118b7 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Nov 2022 08:55:08 +0100 Subject: [PATCH 01/35] Format and lint --- Makefile | 3 + openandroidinstaller/app_state.py | 1 - openandroidinstaller/openandroidinstaller.py | 8 +-- openandroidinstaller/tool_utils.py | 66 ++++++++++---------- openandroidinstaller/utils.py | 13 ++-- openandroidinstaller/views.py | 49 ++++++++++----- pyproject.toml | 6 ++ 7 files changed, 85 insertions(+), 61 deletions(-) diff --git a/Makefile b/Makefile index 2b171390..b8a2f1c8 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,9 @@ install: export: poetry export -f requirements.txt --output requirements.txt +lint: + poetry run ruff openandroidinstaller/ --ignore E501 + test: poetry run pytest tests/ diff --git a/openandroidinstaller/app_state.py b/openandroidinstaller/app_state.py index 414d041f..7f616bc5 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -17,7 +17,6 @@ from flet import ProgressBar from installer_config import _load_config -from loguru import logger class AppState: diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index e2b9d5a9..3f245bfd 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -17,11 +17,8 @@ import sys import webbrowser from pathlib import Path -from time import sleep -from typing import Callable, Optional import flet -import regex as re from app_state import AppState from flet import ( AppBar, @@ -40,7 +37,6 @@ colors, icons, ) -from installer_config import Step from loguru import logger from views import SelectFilesView, StepView, SuccessView, WelcomeView @@ -48,7 +44,7 @@ logger.add("openandroidinstaller.log") # Toggle to True for development purposes -DEVELOPMENT = False +DEVELOPMENT = False DEVELOPMENT_CONFIG = "yuga" # "a3y17lte" # "sargo" @@ -137,7 +133,7 @@ def main(page: Page): # header page.appbar = AppBar( leading=Image( - src=f"/assets/logo-192x192.png", height=40, width=40, border_radius=40 + src="/assets/logo-192x192.png", height=40, width=40, border_radius=40 ), leading_width=56, toolbar_height=72, diff --git a/openandroidinstaller/tool_utils.py b/openandroidinstaller/tool_utils.py index 141c4f20..c76b3239 100644 --- a/openandroidinstaller/tool_utils.py +++ b/openandroidinstaller/tool_utils.py @@ -22,12 +22,10 @@ CalledProcessError, CompletedProcess, check_output, - run, ) from time import sleep from typing import List, Optional -import regex as re from loguru import logger PLATFORM = sys.platform @@ -43,7 +41,9 @@ def run_command(tool: str, command: List[str], bin_path: Path) -> CompletedProce else: full_command = [str(bin_path.joinpath(Path(f"{tool}")))] + command logger.info(f"Run command: {full_command}") - with Popen(full_command, stdout=PIPE, stderr=STDOUT, bufsize=1, universal_newlines=True) as p: + with Popen( + full_command, stdout=PIPE, stderr=STDOUT, bufsize=1, universal_newlines=True + ) as p: for line in p.stdout: logger.info(line.strip()) yield line @@ -59,7 +59,7 @@ def adb_reboot(bin_path: Path) -> bool: if (type(line) == bool) and line: logger.debug("Reboot failed.") yield False - else: + else: yield True @@ -71,7 +71,7 @@ def adb_reboot_bootloader(bin_path: Path) -> bool: if (type(line) == bool) and not line: logger.error("Reboot into bootloader failed.") yield False - return + return sleep(1) yield True # TODO: check if in fastboot mode @@ -92,7 +92,7 @@ def adb_reboot_download(bin_path: Path) -> bool: if (type(line) == bool) and not line: logger.error("Reboot into download mode failed.") yield False - else: + else: # check if in download mode with heimdall? yield True @@ -105,7 +105,7 @@ def adb_sideload(bin_path: Path, target: str) -> bool: if (type(line) == bool) and line: logger.info(f"Sideloading {target} failed.") yield False - else: + else: yield True @@ -121,7 +121,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> if (type(line) == bool) and not line: logger.error("Formatting data failed.") yield False - return + return sleep(1) # wipe some partitions for partition in ["cache", "system"]: @@ -131,7 +131,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> if (type(line) == bool) and not line: logger.error(f"Wiping {partition} failed.") yield False - return + return # activate sideload logger.info("Wiping is done, now activate sideload.") for line in run_command("adb", ["shell", "twrp", "sideload"], bin_path): @@ -139,7 +139,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> if (type(line) == bool) and not line: logger.error("Activating sideload failed.") yield False - return + return # now flash os image sleep(5) logger.info("Sideload and install os image.") @@ -149,7 +149,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> logger.error(f"Sideloading {target} failed.") # TODO: this might sometimes think it failed, but actually it's fine. So skip for now. # yield False - # return + # return # wipe some cache partitions sleep(7) for partition in ["dalvik", "cache"]: @@ -164,17 +164,17 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> sleep(1) if (type(line) == bool) and not line: yield False - return + return break # finally reboot into os sleep(5) logger.info("Reboot into OS.") - for line in run_command("adb", ["reboot"], bin_path): # "shell", "twrp", + for line in run_command("adb", ["reboot"], bin_path): # "shell", "twrp", yield line if (type(line) == bool) and not line: logger.error("Rebooting failed.") yield False - return + return else: yield True @@ -187,67 +187,69 @@ def fastboot_unlock_with_code(bin_path: Path, unlock_code: str) -> bool: if (type(line) == bool) and not line: logger.error(f"Unlocking with code {unlock_code} failed.") yield False - else: + else: yield True def fastboot_unlock(bin_path: Path) -> bool: """Unlock the device with fastboot and without code.""" - logger.info(f"Unlock the device with fastboot.") + logger.info("Unlock the device with fastboot.") for line in run_command("adb", ["flashing", "unlock"], bin_path): yield line if (type(line) == bool) and not line: - logger.error(f"Unlocking failed.") + logger.error("Unlocking failed.") yield False - else: + else: yield True def fastboot_oem_unlock(bin_path: Path) -> bool: """OEM unlock the device with fastboot and without code.""" - logger.info(f"OEM unlocking the device with fastboot.") + logger.info("OEM unlocking the device with fastboot.") for line in run_command("adb", ["oem", "unlock"], bin_path): yield line if (type(line) == bool) and not line: - logger.error(f"OEM unlocking failed.") + logger.error("OEM unlocking failed.") yield False - else: + else: yield True def fastboot_reboot(bin_path: Path) -> bool: """Reboot with fastboot""" - logger.info(f"Rebooting device with fastboot.") + logger.info("Rebooting device with fastboot.") for line in run_command("fastboot", ["reboot"], bin_path): yield line if (type(line) == bool) and not line: - logger.error(f"Rebooting with fastboot failed.") + logger.error("Rebooting with fastboot failed.") yield False - else: + else: yield True def fastboot_flash_recovery(bin_path: Path, recovery: str) -> bool: """Temporarily, flash custom recovery with fastboot.""" - logger.info(f"Flash custom recovery with fastboot.") + logger.info("Flash custom recovery with fastboot.") for line in run_command("fastboot", ["boot", f"{recovery}"], bin_path): yield line if (type(line) == bool) and not line: - logger.error(f"Flashing recovery failed.") + logger.error("Flashing recovery failed.") yield False - else: + else: yield True def heimdall_flash_recovery(bin_path: Path, recovery: str) -> bool: """Temporarily, flash custom recovery with heimdall.""" - logger.info(f"Flash custom recovery with heimdall.") - for line in run_command("heimdall", ["flash", "--no-reboot", "--RECOVERY", f"{recovery}"], bin_path): + logger.info("Flash custom recovery with heimdall.") + for line in run_command( + "heimdall", ["flash", "--no-reboot", "--RECOVERY", f"{recovery}"], bin_path + ): yield line if (type(line) == bool) and not line: - logger.error(f"Flashing recovery with heimdall failed.") + logger.error("Flashing recovery with heimdall failed.") yield False - else: + else: yield True @@ -287,5 +289,5 @@ def search_device(platform: str, bin_path: Path) -> Optional[str]: logger.info(device_code) return device_code except CalledProcessError: - logger.error(f"Failed to detect a device.") + logger.error("Failed to detect a device.") return None diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index 7830199f..7065f359 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -17,7 +17,6 @@ from typing import Optional import requests -from installer_config import Step from loguru import logger @@ -56,12 +55,16 @@ def image_recovery_works_with_device( logger.info(f"Image works with device: {supported_devices}") recovery_file_name = recovery_path.split("/")[-1] - if (device_code in supported_devices) and ( - device_code in recovery_file_name - ) and ("twrp" in recovery_file_name): + if ( + (device_code in supported_devices) + and (device_code in recovery_file_name) + and ("twrp" in recovery_file_name) + ): logger.success("Device supported by the image and recovery.") return True else: - logger.error(f"Recovery {recovery_file_name} and/or image {image_path.split('/')[-1]} are not supported or don't match.") + logger.error( + f"Recovery {recovery_file_name} and/or image {image_path.split('/')[-1]} are not supported or don't match." + ) return False diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index d3552e48..305e4c45 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -19,7 +19,6 @@ from pathlib import Path from loguru import logger -import flet from flet import ( AlertDialog, alignment, @@ -331,7 +330,7 @@ def build(self): "Download TWRP recovery", icon=icons.DOWNLOAD_OUTLINED, on_click=lambda _: webbrowser.open( - f"https://dl.twrp.me/{self.state.config.metadata.get('devicecode')}" + f"https://dl.twrp.me/{self.state.config.metadata.get('devicecode')}" ), expand=True, ), @@ -350,7 +349,9 @@ def build(self): # attach the controls for uploading image and recovery self.right_view.controls.extend( [ - Text("Now select the operating system image and recovery (note, that only TWRP recoveries are supported):"), + Text( + "Now select the operating system image and recovery (note, that only TWRP recoveries are supported):" + ), Row( [ ElevatedButton( @@ -428,7 +429,9 @@ def enable_button_if_ready(self, e): recovery_path=self.state.recovery_path, ): # if image and recovery work for device allow to move on, otherwise display message - logger.error("Image and recovery don't work with the device. Please select different ones.") + logger.error( + "Image and recovery don't work with the device. Please select different ones." + ) self.info_field.controls = [ Text( "Image and recovery don't work with the device. Please select different ones." @@ -494,9 +497,7 @@ def build(self): ) # add terminal box if enabled if self.state.advanced: - self.right_view.controls.append( - Row([self.terminal_box]) - ) + self.right_view.controls.append(Row([self.terminal_box])) elif self.step.type == "call_button_with_input": self.confirm_button.disabled = True self.call_button = call_button( @@ -543,7 +544,11 @@ def call_to_phone(self, e, command: str): # display a progress bar to show something is happening self.right_view.controls.append( Row( - [ProgressBar(width=600, color="#00d886", bgcolor="#eeeeee", bar_height=16)], + [ + ProgressBar( + width=600, color="#00d886", bgcolor="#eeeeee", bar_height=16 + ) + ], alignment="center", ), ) @@ -566,35 +571,45 @@ def call_to_phone(self, e, command: str): self.terminal_box.update() success = line elif command == "adb_sideload": - for line in adb_sideload(bin_path=self.state.bin_path, target=self.state.image_path): + for line in adb_sideload( + bin_path=self.state.bin_path, target=self.state.image_path + ): if self.state.advanced and (type(line) == str) and line.strip(): self.terminal_box.content.controls.append(Text(f">{line.strip()}")) self.terminal_box.update() success = line elif command == "adb_twrp_wipe_and_install": for line in adb_twrp_wipe_and_install( - bin_path=self.state.bin_path, - target=self.state.image_path, - config_path=self.state.config_path.joinpath(Path(f"{self.state.config.metadata.get('devicecode')}.yaml")) - ): + bin_path=self.state.bin_path, + target=self.state.image_path, + config_path=self.state.config_path.joinpath( + Path(f"{self.state.config.metadata.get('devicecode')}.yaml") + ), + ): if self.state.advanced and (type(line) == str) and line.strip(): self.terminal_box.content.controls.append(Text(f">{line.strip()}")) self.terminal_box.update() success = line elif command == "fastboot_flash_recovery": - for line in fastboot_flash_recovery(bin_path=self.state.bin_path, recovery=self.state.recovery_path): + for line in fastboot_flash_recovery( + bin_path=self.state.bin_path, recovery=self.state.recovery_path + ): if self.state.advanced and (type(line) == str) and line.strip(): self.terminal_box.content.controls.append(Text(f">{line.strip()}")) self.terminal_box.update() success = line elif command == "fastboot_unlock_with_code": - for line in fastboot_unlock_with_code(bin_path=self.state.bin_path, unlock_code=self.inputtext.value): + for line in fastboot_unlock_with_code( + bin_path=self.state.bin_path, unlock_code=self.inputtext.value + ): if self.state.advanced and (type(line) == str) and line.strip(): self.terminal_box.content.controls.append(Text(f">{line.strip()}")) self.terminal_box.update() success = line elif command == "heimdall_flash_recovery": - for line in heimdall_flash_recovery(bin_path=self.state.bin_path, recovery=self.state.recovery_path): + for line in heimdall_flash_recovery( + bin_path=self.state.bin_path, recovery=self.state.recovery_path + ): if self.state.advanced and (type(line) == str) and line.strip(): self.terminal_box.content.controls.append(Text(f">{line.strip()}")) self.terminal_box.update() @@ -605,7 +620,7 @@ def call_to_phone(self, e, command: str): # update the view accordingly if not success: - # pop the progress bar + # pop the progress bar self.right_view.controls.pop() self.right_view.controls.append( Text( diff --git a/pyproject.toml b/pyproject.toml index a596c424..b08e7e05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,3 +27,9 @@ py7zr = "^0.20.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.ruff] +line-length = 88 + +# Never enforce `E501`. line length +exclude = ["E501"] From 1a23301a8069d98e60ee5ad6649dd8c630d060ce Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 21 Nov 2022 10:35:54 +0100 Subject: [PATCH 02/35] Fairphone3 now uses flashing unlock to unlock the bootloader --- openandroidinstaller/assets/configs/FP3.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openandroidinstaller/assets/configs/FP3.yaml b/openandroidinstaller/assets/configs/FP3.yaml index e53b6bdb..31810217 100644 --- a/openandroidinstaller/assets/configs/FP3.yaml +++ b/openandroidinstaller/assets/configs/FP3.yaml @@ -20,7 +20,7 @@ steps: Then press 'Confirm and continue' here. - type: call_button content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. - command: fastboot_oem_unlock + command: fastboot_unlock - type: confirm_button content: > Follow the instructions on the Fairphone screen. This command will wipe all the personal data on your phone. From b7784879cbc478c482d773fbba97f6159eb66915 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 21 Nov 2022 13:42:43 +0100 Subject: [PATCH 03/35] Fix bootloader unlock issue with fastboot --- openandroidinstaller/tool_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openandroidinstaller/tool_utils.py b/openandroidinstaller/tool_utils.py index c76b3239..417706c1 100644 --- a/openandroidinstaller/tool_utils.py +++ b/openandroidinstaller/tool_utils.py @@ -182,7 +182,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> def fastboot_unlock_with_code(bin_path: Path, unlock_code: str) -> bool: """Unlock the device with fastboot and code given.""" logger.info(f"Unlock the device with fastboot and code: {unlock_code}.") - for line in run_command("adb", ["oem", "unlock", f"{unlock_code}"], bin_path): + for line in run_command("fastboot", ["oem", "unlock", f"{unlock_code}"], bin_path): yield line if (type(line) == bool) and not line: logger.error(f"Unlocking with code {unlock_code} failed.") @@ -194,7 +194,7 @@ def fastboot_unlock_with_code(bin_path: Path, unlock_code: str) -> bool: def fastboot_unlock(bin_path: Path) -> bool: """Unlock the device with fastboot and without code.""" logger.info("Unlock the device with fastboot.") - for line in run_command("adb", ["flashing", "unlock"], bin_path): + for line in run_command("fastboot", ["flashing", "unlock"], bin_path): yield line if (type(line) == bool) and not line: logger.error("Unlocking failed.") @@ -206,7 +206,7 @@ def fastboot_unlock(bin_path: Path) -> bool: def fastboot_oem_unlock(bin_path: Path) -> bool: """OEM unlock the device with fastboot and without code.""" logger.info("OEM unlocking the device with fastboot.") - for line in run_command("adb", ["oem", "unlock"], bin_path): + for line in run_command("fastboot", ["oem", "unlock"], bin_path): yield line if (type(line) == bool) and not line: logger.error("OEM unlocking failed.") From 31341e21df8bfaa0a5055c5156eefc3ee1916c64 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 21 Nov 2022 14:42:11 +0100 Subject: [PATCH 04/35] Disable callbutton while command is running; clean error text at retry --- openandroidinstaller/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index 305e4c45..501da2e1 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -538,6 +538,8 @@ def call_to_phone(self, e, command: str): Some parts of the command are changed by placeholders. """ + # disable the call button while the command is running + self.call_button.disabled = True # reset terminal output if self.state.advanced: self.terminal_box.content.controls = [] @@ -620,8 +622,13 @@ def call_to_phone(self, e, command: str): # update the view accordingly if not success: + # enable call button to retry + self.call_button.disabled = False # pop the progress bar self.right_view.controls.pop() + # also remove the last error text if it happened + if isinstance(self.right_view.controls[-1], Text): + self.right_view.controls.pop() self.right_view.controls.append( Text( f"Command {command} failed! Try again or make sure everything is setup correctly." @@ -632,6 +639,7 @@ def call_to_phone(self, e, command: str): logger.success(f"Command {command} run successfully. Allow to continue.") # pop the progress bar self.right_view.controls.pop() + # emable the confirm buton and disable the call button self.confirm_button.disabled = False self.call_button.disabled = True self.view.update() From 64eafa41e3b668a2d8f2bff7918d51b1e24d8cb7 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 22 Nov 2022 14:21:26 +0100 Subject: [PATCH 05/35] Minor updates to readme and logging --- README.md | 16 ++++++++-------- openandroidinstaller/openandroidinstaller.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index edb2ffe5..f112fb99 100644 --- a/README.md +++ b/README.md @@ -38,21 +38,21 @@ Vendor | Device Name | CodeName | Models | Status ---|---|---|---|--- Samsung | Galaxy J7 2015 | j7elte | | tested Samsung | Galaxy A3 2017 | a3y17lte | SM-A320FL | tested -Samsung | Galaxy A5 2016 | a5xelte | SM-A510F | tested +Samsung | Galaxy A5 2016 | [a5xelte](https://wiki.lineageos.org/devices/a5xelte/) | SM-A510F | tested Samsung | Galaxy A7 2016 | a7xelte | | tested -Samsung | Galaxy S7 | herolte | SM-G930F | tested -Samsung | Galaxy S9 | starlte | | tested +Samsung | Galaxy S7 | [herolte](https://wiki.lineageos.org/devices/herolte/) | SM-G930F | tested +Samsung | Galaxy S9 | [starlte](https://wiki.lineageos.org/devices/starlte/) | | tested Samsung | Galaxy S10 | beyond1lte | | tested -Google | Pixel 3a | sargo | sargo | tested +Google | Pixel 3a | [sargo](https://wiki.lineageos.org/devices/sargo/) | sargo | tested Google | Pixel 4 | flame | flame | tested Google | Pixel 4a | sunfish | sunfish | tested Google | Pixel 5 | redfin | redfin | tested Google | Pixel 5a | barbet | barbet | tested -Sony | Xperia Z | yuga | C6603 | tested -Sony | Xperia Z3 | z3 | | tested +Sony | Xperia Z | [yuga](https://wiki.lineageos.org/devices/yuga/) | C6603 | tested +Sony | Xperia Z3 | [z3](https://wiki.lineageos.org/devices/z3/) | | tested Sony | Xperia ZX | kagura | | planned -Fairphone | Fairphone 2 | FP2 | | tested -Fairphone | Fairphone 3 | FP3 | | tested +Fairphone | Fairphone 2 | [FP2](https://wiki.lineageos.org/devices/FP2/) | | tested +Fairphone | Fairphone 3 | [FP3](https://wiki.lineageos.org/devices/FP3/) | | tested Motorola | moto G5 | cedric | | planned Motorola | moto g7 power | ocean | | under development OnePlus | 6 | enchilada | | under development diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 3f245bfd..3d828c32 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -39,6 +39,7 @@ ) from loguru import logger from views import SelectFilesView, StepView, SuccessView, WelcomeView +from tool_utils import run_command # where to write the logs logger.add("openandroidinstaller.log") @@ -119,8 +120,24 @@ def confirm(self, e): self.view.update() +def log_version_infos(bin_path): + """Log the version infos of adb, fastboot and heimdall.""" + # adb + adbversion = [line for line in run_command("adb", ["version"], bin_path)] + adbversion = '\n'.join(adbversion[:1]) + logger.info(f"{adbversion}") + # fastboot + fbversion = [line for line in run_command("fastboot", ["--version"], bin_path)] + logger.info(f"{fbversion[0]}") + # heimdall + hdversion = [line for line in run_command("heimdall", ["info"], bin_path)] + logger.info(f"Heimdall version: {hdversion[0]}") + + def main(page: Page): logger.info(f"Running OpenAndroidInstaller on {PLATFORM}") + log_version_infos(bin_path=BIN_PATH) + logger.info(20*"-") # Configure the application base page page.title = "OpenAndroidInstaller" page.window_height = 780 From fe135b463375a3b13630d49c693154866073b7ba Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 22 Nov 2022 15:23:51 +0100 Subject: [PATCH 06/35] Add another steptype for requirements and get them from config. --- openandroidinstaller/app_state.py | 2 +- .../assets/configs/sargo.yaml | 2 + .../assets/configs/starlte.yaml | 2 + openandroidinstaller/assets/configs/z3.yaml | 2 + openandroidinstaller/installer_config.py | 11 +- openandroidinstaller/openandroidinstaller.py | 20 +-- openandroidinstaller/views.py | 136 ++++++++++++++++++ 7 files changed, 165 insertions(+), 10 deletions(-) diff --git a/openandroidinstaller/app_state.py b/openandroidinstaller/app_state.py index 7f616bc5..8a67e66a 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -66,6 +66,6 @@ def load_config(self, device_code: str): def increment_progressbar(self): """Increment the progressbar and step counter.""" self.progressbar.value = (self.num_steps - 1) / ( - self.num_total_steps + 2 + self.num_total_steps + 3 ) # don't show on the first step self.num_steps += 1 # increase the step counter diff --git a/openandroidinstaller/assets/configs/sargo.yaml b/openandroidinstaller/assets/configs/sargo.yaml index 8309c267..5da5ca9d 100644 --- a/openandroidinstaller/assets/configs/sargo.yaml +++ b/openandroidinstaller/assets/configs/sargo.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Pixel 3a devicecode: sargo +requirements: + android: 12.1.0 steps: unlock_bootloader: - type: confirm_button diff --git a/openandroidinstaller/assets/configs/starlte.yaml b/openandroidinstaller/assets/configs/starlte.yaml index f7c3534b..e3e8b77f 100644 --- a/openandroidinstaller/assets/configs/starlte.yaml +++ b/openandroidinstaller/assets/configs/starlte.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Samsung Galaxy S9 devicecode: starlte +requirements: + android: 10 steps: unlock_bootloader: flash_recovery: diff --git a/openandroidinstaller/assets/configs/z3.yaml b/openandroidinstaller/assets/configs/z3.yaml index 4af08a39..54eb3553 100644 --- a/openandroidinstaller/assets/configs/z3.yaml +++ b/openandroidinstaller/assets/configs/z3.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Sony Xperia Z3 devicecode: z3 +requirements: + firmware: 23.5.A.1.291 steps: unlock_bootloader: - type: confirm_button diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 42339499..1c3d2be7 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -49,11 +49,13 @@ def __init__( flash_recovery: List[Step], install_os: List[Step], metadata: dict, + requirements: dict, ): self.unlock_bootloader = unlock_bootloader self.flash_recovery = flash_recovery self.install_os = install_os self.metadata = metadata + self.requirements = requirements @classmethod def from_file(cls, path): @@ -64,6 +66,7 @@ def from_file(cls, path): config = dict(raw_config) raw_steps = config["steps"] metadata = config["metadata"] + requirements = config.get("requirements", None) else: logger.info("Validation of config failed.") return None @@ -86,7 +89,9 @@ def from_file(cls, path): Step(**raw_step, title="Install OS") for raw_step in raw_steps.get("install_os", []) ] - return cls(unlock_bootloader, flash_recovery, install_os, metadata) + return cls( + unlock_bootloader, flash_recovery, install_os, metadata, requirements + ) def _load_config(device_code: str, config_path: Path) -> Optional[InstallerConfig]: @@ -139,6 +144,10 @@ def validate_config(config: str) -> bool: "devicename": str, "devicecode": str, }, + schema.Optional("requirements"): { + schema.Optional("android"): str, + schema.Optional("firmware"): str, + }, "steps": { "unlock_bootloader": schema.Or(None, [step_schema]), "flash_recovery": [step_schema], diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 3d828c32..8b4649e5 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -38,15 +38,15 @@ icons, ) from loguru import logger -from views import SelectFilesView, StepView, SuccessView, WelcomeView +from views import SelectFilesView, StepView, SuccessView, WelcomeView, RequirementsView from tool_utils import run_command # where to write the logs logger.add("openandroidinstaller.log") # Toggle to True for development purposes -DEVELOPMENT = False -DEVELOPMENT_CONFIG = "yuga" # "a3y17lte" # "sargo" +DEVELOPMENT = True +DEVELOPMENT_CONFIG = "z3" # "a3y17lte" # "sargo" PLATFORM = sys.platform @@ -75,16 +75,20 @@ def __init__(self): self.view = Column(expand=True, width=1200) # create default starter views - welcome = WelcomeView( + welcome_view = WelcomeView( on_confirm=self.confirm, state=self.state, ) - select_files = SelectFilesView( + requirements_view = RequirementsView( + on_confirm=self.confirm, + state=self.state, + ) + select_files_view = SelectFilesView( on_confirm=self.confirm, state=self.state, ) # ordered to allow for pop - self.default_views = [select_files, welcome] + self.default_views = [select_files_view, requirements_view, welcome_view] # create the final success view self.final_view = SuccessView(state=self.state) @@ -124,7 +128,7 @@ def log_version_infos(bin_path): """Log the version infos of adb, fastboot and heimdall.""" # adb adbversion = [line for line in run_command("adb", ["version"], bin_path)] - adbversion = '\n'.join(adbversion[:1]) + adbversion = "\n".join(adbversion[:1]) logger.info(f"{adbversion}") # fastboot fbversion = [line for line in run_command("fastboot", ["--version"], bin_path)] @@ -137,7 +141,7 @@ def log_version_infos(bin_path): def main(page: Page): logger.info(f"Running OpenAndroidInstaller on {PLATFORM}") log_version_infos(bin_path=BIN_PATH) - logger.info(20*"-") + logger.info(100 * "-") # Configure the application base page page.title = "OpenAndroidInstaller" page.window_height = 780 diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index 501da2e1..11ad0486 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -67,7 +67,9 @@ class BaseView(UserControl): def __init__(self, state: AppState, image: str = "placeholder.png"): super().__init__() self.state = state + # right part of the display, add content here. self.right_view = Column(expand=True) + # left part of the display: used for displaying the images self.left_view = Column( width=600, controls=[Image(src=f"/assets/imgs/{image}")], @@ -81,6 +83,140 @@ def __init__(self, state: AppState, image: str = "placeholder.png"): ) +class RequirementsView(BaseView): + """View to display requirements and ask for confirmation.""" + + def __init__( + self, + state: AppState, + on_confirm: Callable, + ): + super().__init__(state=state) + self.on_confirm = on_confirm + + def build(self): + self.continue_button = ElevatedButton( + "Continue", + on_click=self.on_confirm, + icon=icons.NEXT_PLAN_OUTLINED, + disabled=True, + expand=True, + ) + + # build up the main view + self.right_view.controls.extend( + [ + get_title("Check the Requirements"), + Text( + "Before continuing you need to check some requirements to progress. Please read the instructions and check the boxes if everything is fine." + ), + Divider(), + ] + ) + self.checkboxes = [] + + def enable_continue_button(e): + """Enable the continue button if all checkboxes are ticked.""" + for checkbox in self.checkboxes: + if not checkbox.value: + logger.info(checkbox) + self.continue_button.disabled = True + return + logger.info("All requirements ticked. Allow to continue") + self.continue_button.disabled = False + self.right_view.update() + + # check if there are additional requirements given in the config + if self.state.config.requirements: + # android version + required_android_version = self.state.config.requirements.get("android") + if required_android_version: + android_checkbox = Checkbox( + label="The required android version is installed. (Or I know the risk of continuing)", + on_change=enable_continue_button, + ) + android_version_check = Column( + [ + Markdown( + f""" +#### Android Version {required_android_version}: +Before following these instructions please ensure that the device is currently using Android {required_android_version} firmware. +If the vendor provided multiple updates for that version, e.g. security updates, make sure you are on the latest! +If your current installation is newer or older than Android {required_android_version}, please upgrade or downgrade to the required +version before proceeding (guides can be found on the internet!). + """ + ), + android_checkbox, + ] + ) + self.checkboxes.append(android_checkbox) + self.right_view.controls.append(android_version_check) + + # firmware version + required_firmware_version = self.state.config.requirements.get("firmware") + if required_firmware_version: + firmware_checkbox = Checkbox( + label="The required firmware version is installed. (Or I know the risk of continuing)", + on_change=enable_continue_button, + ) + firmware_version_check = Column( + [ + Markdown( + f""" +#### Firmware Version {required_firmware_version}: +Before following these instructions please ensure that the device is on firmware version {required_firmware_version}. +To discern this, you can run the command `adb shell getprop ro.build.display.id` on the stock ROM. +If the device is not on the specified version, please follow the instructions below to install it. + """ + ), + firmware_checkbox, + ] + ) + self.checkboxes.append(firmware_checkbox) + self.right_view.controls.append(firmware_version_check) + + # default requirements: battery level + battery_checkbox = Checkbox( + label="The battery level is over 80%.", + on_change=enable_continue_button, + ) + battery_version_check = Column( + [ + Markdown( + f""" +#### Battery level over 80% +Before continuing make sure your device battery level is above 80%. + """ + ), + battery_checkbox, + ] + ) + self.checkboxes.append(battery_checkbox) + self.right_view.controls.append(battery_version_check) + + # default requirement: disable lock code and fingerprint + lock_checkbox = Checkbox( + label="No lock code or fingerprint lock enabled.", + on_change=enable_continue_button, + ) + lock_check = Column( + [ + Markdown( + f""" +#### Disable all device lock codes and fingerprint locks. + """ + ), + lock_checkbox, + ] + ) + self.checkboxes.append(lock_checkbox) + self.right_view.controls.append(lock_check) + + # add the final confirm and continue button + self.right_view.controls.append(Row([self.continue_button], alignment="center")) + return self.view + + class WelcomeView(BaseView): def __init__( self, From 674278bcf48f14a651c481dc95c1aa3c4448d6c2 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 23 Nov 2022 09:57:56 +0100 Subject: [PATCH 07/35] WIP: Sideload a different file to help TWRP on ab devices --- openandroidinstaller/assets/helper.txt | 2 ++ openandroidinstaller/tool_utils.py | 2 +- openandroidinstaller/views.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 openandroidinstaller/assets/helper.txt diff --git a/openandroidinstaller/assets/helper.txt b/openandroidinstaller/assets/helper.txt new file mode 100644 index 00000000..a404baec --- /dev/null +++ b/openandroidinstaller/assets/helper.txt @@ -0,0 +1,2 @@ +This file is sideloaded by OpenAndroidInstaller to help with a TWRP issue on A/B-partioned devices. +Please visit https://github.com/openandroidinstaller-dev/openandroidinstaller to find out more. \ No newline at end of file diff --git a/openandroidinstaller/tool_utils.py b/openandroidinstaller/tool_utils.py index 417706c1..87e787ee 100644 --- a/openandroidinstaller/tool_utils.py +++ b/openandroidinstaller/tool_utils.py @@ -159,7 +159,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> if (type(line) == bool) and not line: logger.error(f"Wiping {partition} failed.") # TODO: if this fails, a fix can be to just sideload something and then adb reboot - for line in run_command("adb", ["sideload", str(config_path)], bin_path): + for line in run_command("adb", ["sideload", str(config_path.parent.parent) + "/helper.txt"], bin_path): yield line sleep(1) if (type(line) == bool) and not line: diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index 501da2e1..ef86b9e4 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -655,6 +655,7 @@ def build( self.right_view.controls = [ get_title("Installation completed successfully!"), self.state.progressbar, + Text("Now your devices boots into the new OS. Have fun with it!"), Row( [ ElevatedButton( From c5b6656c535f4858b36336e11e8c1a120473428a Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 23 Nov 2022 12:32:12 +0100 Subject: [PATCH 08/35] Restructure welcome page --- openandroidinstaller/openandroidinstaller.py | 6 +- openandroidinstaller/tool_utils.py | 6 +- openandroidinstaller/views.py | 91 ++++++++++++++------ 3 files changed, 72 insertions(+), 31 deletions(-) diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 8b4649e5..e7e4366d 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -45,8 +45,8 @@ logger.add("openandroidinstaller.log") # Toggle to True for development purposes -DEVELOPMENT = True -DEVELOPMENT_CONFIG = "z3" # "a3y17lte" # "sargo" +DEVELOPMENT = False +DEVELOPMENT_CONFIG = "sargo" # "a3y17lte" # "sargo" PLATFORM = sys.platform @@ -144,7 +144,7 @@ def main(page: Page): logger.info(100 * "-") # Configure the application base page page.title = "OpenAndroidInstaller" - page.window_height = 780 + page.window_height = 820 page.window_width = int(1.77 * page.window_height) page.window_top = 100 page.window_left = 120 diff --git a/openandroidinstaller/tool_utils.py b/openandroidinstaller/tool_utils.py index 87e787ee..1f368372 100644 --- a/openandroidinstaller/tool_utils.py +++ b/openandroidinstaller/tool_utils.py @@ -159,7 +159,11 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> if (type(line) == bool) and not line: logger.error(f"Wiping {partition} failed.") # TODO: if this fails, a fix can be to just sideload something and then adb reboot - for line in run_command("adb", ["sideload", str(config_path.parent.parent) + "/helper.txt"], bin_path): + for line in run_command( + "adb", + ["sideload", str(config_path.parent.parent) + "/helper.txt"], + bin_path, + ): yield line sleep(1) if (type(line) == bool) and not line: diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index 62373e57..9e67ef28 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -234,13 +234,8 @@ def build(self): disabled=True, expand=True, ) - self.device_name = Text("") - self.config_found_box = Checkbox( - label="Device config found:", - value=False, - disabled=True, - label_position="left", - ) + + # dialog box to help with developer options self.dlg_help_developer_options = AlertDialog( modal=True, title=Text("How to enable developer options and OEM unlocking"), @@ -297,6 +292,12 @@ def check_advanced_box(e): disabled=False, ) + # inform the user about the device detection + self.device_name = Text("", weight="bold") + self.device_detection_infobox = Row( + [Text("Detected device:"), self.device_name] + ) + # build up the main view self.right_view.controls.extend( [ @@ -305,17 +306,28 @@ def check_advanced_box(e): "We will walk you through the installation process nice and easy." ), Divider(), - Text( - "Before you continue, make sure your devices is on the latest system update. Also make sure you have a backup of all your important data, since this procedure will erase all data from the phone. Please store the backup not on the phone! Note, that vendor specific back-ups might not work on LineageOS!" + Markdown( + """ +Before you continue, make sure +- your devices is on the latest system update. +- you have a backup of all your important data, since this procedure will **erase all data from the phone**. +- to not store the backup not the phone! + +Please note, that vendor specific back-ups will most likely not work on LineageOS! + """ ), Divider(), - Text( - "Enable USB debugging and OEM unlocking on your device by enabling developer options." + Markdown( + """ +To get started you need to +- **enable developer options** on your device +- and then **enable USB debugging** and **OEM unlocking** in the developer options. + """ ), Row( [ ElevatedButton( - "How do I enable developer mode?", + "How do I enable developer options?", on_click=self.open_developer_options_dlg, expand=True, tooltip="Get help to enable developer options and OEM unlocking.", @@ -323,14 +335,19 @@ def check_advanced_box(e): ] ), Divider(), - Text( - "Now connect your device to this computer via USB and allow USB debugging in the pop-up on your phone. Then press 'Search device'. When everything works correctly you should see your device name here." + Markdown( + """ +Now +- **connect your device to this computer via USB** and +- **allow USB debugging in the pop-up on your phone**. +- Then **press the button 'Search device'**. +When everything works correctly you should see your device name here and you can continue. + """ ), Divider(), Column( [ - Row([Text("Detected device:"), self.device_name]), - self.config_found_box, + self.device_detection_infobox, Row([self.bootloader_checkbox, self.advanced_checkbox]), ] ), @@ -382,32 +399,40 @@ def search_devices(self, e): if device_code: device_code = device_code_mapping.get(device_code, device_code) self.device_name.value = device_code + self.device_name.color = colors.BLACK else: logger.info("No device detected! Connect to USB and try again.") self.device_name.value = ( "No device detected! Connect to USB and try again." ) + self.device_name.color = colors.RED # load the config, if a device is detected if device_code: self.device_name.value = device_code # load config from file self.state.load_config(device_code) - device_name = self.state.config.metadata.get( - "devicename", "No device name in config." - ) + if self.state.config: + device_name = self.state.config.metadata.get( + "devicename", "No device name in config." + ) + else: + device_name = None # display success in the application if device_name: - self.config_found_box.value = True self.continue_button.disabled = False self.bootloader_checkbox.disabled = False # overwrite the text field with the real name from the config self.device_name.value = f"{device_name} (code: {device_code})" + self.device_name.color = colors.GREEN else: # failed to load config logger.error(f"Failed to load config for {device_code}.") - self.device_name.value = f"Failed to load config for {device_code}." + self.device_name.value = ( + f"Failed to load config for device with code {device_code}." + ) + self.device_name.color = colors.RED self.view.update() @@ -705,7 +730,9 @@ def call_to_phone(self, e, command: str): if command in cmd_mapping.keys(): for line in cmd_mapping.get(command)(bin_path=self.state.bin_path): if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append(Text(f">{line.strip()}")) + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) self.terminal_box.update() success = line elif command == "adb_sideload": @@ -713,7 +740,9 @@ def call_to_phone(self, e, command: str): bin_path=self.state.bin_path, target=self.state.image_path ): if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append(Text(f">{line.strip()}")) + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) self.terminal_box.update() success = line elif command == "adb_twrp_wipe_and_install": @@ -725,7 +754,9 @@ def call_to_phone(self, e, command: str): ), ): if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append(Text(f">{line.strip()}")) + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) self.terminal_box.update() success = line elif command == "fastboot_flash_recovery": @@ -733,7 +764,9 @@ def call_to_phone(self, e, command: str): bin_path=self.state.bin_path, recovery=self.state.recovery_path ): if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append(Text(f">{line.strip()}")) + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) self.terminal_box.update() success = line elif command == "fastboot_unlock_with_code": @@ -741,7 +774,9 @@ def call_to_phone(self, e, command: str): bin_path=self.state.bin_path, unlock_code=self.inputtext.value ): if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append(Text(f">{line.strip()}")) + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) self.terminal_box.update() success = line elif command == "heimdall_flash_recovery": @@ -749,7 +784,9 @@ def call_to_phone(self, e, command: str): bin_path=self.state.bin_path, recovery=self.state.recovery_path ): if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append(Text(f">{line.strip()}")) + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) self.terminal_box.update() success = line else: From f6421d7e6cc372cc52ab72785248cba3f83b2f39 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Thu, 24 Nov 2022 17:25:09 +0100 Subject: [PATCH 09/35] Add a bit more default pictures for steps --- .../assets/imgs/requirements-default.png | Bin 0 -> 8997 bytes .../assets/imgs/unlock-bootloader-default.png | Bin 0 -> 8329 bytes openandroidinstaller/installer_config.py | 10 ++++++++-- openandroidinstaller/openandroidinstaller.py | 2 +- openandroidinstaller/utils.py | 4 ++-- openandroidinstaller/views.py | 3 +-- 6 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 openandroidinstaller/assets/imgs/requirements-default.png create mode 100644 openandroidinstaller/assets/imgs/unlock-bootloader-default.png diff --git a/openandroidinstaller/assets/imgs/requirements-default.png b/openandroidinstaller/assets/imgs/requirements-default.png new file mode 100644 index 0000000000000000000000000000000000000000..d4088e61caf3090d9be7dae512a31bf84aa91c41 GIT binary patch literal 8997 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 literal 0 HcmV?d00001 diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 1c3d2be7..97a058a9 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -23,13 +23,19 @@ class Step: + """Class representing on step in the installer.""" + + default_images = { + "Unlock the bootloader": "unlock-bootloader-default.png", + } + def __init__( self, title: str, type: str, content: str, command: str = None, - img: str = "placeholder.png", + img: str = None, allow_skip: bool = False, link: str = None, ): @@ -37,7 +43,7 @@ def __init__( self.type = type self.content = content self.command = command - self.img = img + self.img = img if img else self.default_images.get(title, "placeholder.png") self.allow_skip = allow_skip self.link = link diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index e7e4366d..9a6a1f58 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -45,7 +45,7 @@ logger.add("openandroidinstaller.log") # Toggle to True for development purposes -DEVELOPMENT = False +DEVELOPMENT = True DEVELOPMENT_CONFIG = "sargo" # "a3y17lte" # "sargo" diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index 7065f359..3fe91c33 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -21,12 +21,12 @@ def get_download_link(devicecode: str) -> Optional[str]: - """Check if a lineageOS version for this device exists on lineageosroms.com and return the respective download link.""" + """Check if a lineageOS version for this device exists on download.lineageos.com and return the respective download link.""" url = f"https://download.lineageos.org/{devicecode.lower()}" try: logger.info(f"Checking {url}") # Get Url - res = requests.get(url) + res = requests.get(url, timeout=5) # if the request succeeds if res.status_code == 200: logger.info(f"{url} exists.") diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index 9e67ef28..6d021bee 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -91,7 +91,7 @@ def __init__( state: AppState, on_confirm: Callable, ): - super().__init__(state=state) + super().__init__(state=state, image="requirements-default.png") self.on_confirm = on_confirm def build(self): @@ -119,7 +119,6 @@ def enable_continue_button(e): """Enable the continue button if all checkboxes are ticked.""" for checkbox in self.checkboxes: if not checkbox.value: - logger.info(checkbox) self.continue_button.disabled = True return logger.info("All requirements ticked. Allow to continue") From c537f97480c3bbc9980520bdf13120b63937ffc3 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 10:55:31 +0100 Subject: [PATCH 10/35] Update sargo config a bit --- openandroidinstaller/assets/configs/sargo.yaml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/openandroidinstaller/assets/configs/sargo.yaml b/openandroidinstaller/assets/configs/sargo.yaml index 5da5ca9d..1336bb07 100644 --- a/openandroidinstaller/assets/configs/sargo.yaml +++ b/openandroidinstaller/assets/configs/sargo.yaml @@ -6,17 +6,12 @@ requirements: android: 12.1.0 steps: unlock_bootloader: - - type: confirm_button + - type: call_button content: > As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone how to start and run an operating system (like Android). Your device should be turned on. - - type: call_button - content: Press 'Confirm and run' to reboot into the bootloader. + Press 'Confirm and run' to reboot into the bootloader. command: adb_reboot_bootloader - - type: confirm_button - content: > - Select 'Restart bootloader' on your smartphone screen by pressing the volume button and the confirm by pushing the power button. - Then press 'Confirm and continue' here. - type: call_button content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. command: fastboot_unlock @@ -28,17 +23,14 @@ steps: content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. command: fastboot_reboot - type: confirm_button - content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable USB debugging to continue. + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. flash_recovery: - - type: confirm_button + - type: call_button content: > Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, adapting and repairing of the operating system. - - type: call_button - content: Once the device is fully booted, you need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. command: adb_reboot_bootloader - - type: confirm_button - content: Select 'Restart bootloader' on your smartphone screen. Then confirm to continue. - type: call_button content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. command: fastboot_flash_recovery From 9c09b5fd9b351fc1f5eb0ad6cf104886bdf5508d Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 11:11:41 +0100 Subject: [PATCH 11/35] Use switches in welcome view --- openandroidinstaller/openandroidinstaller.py | 5 +++-- openandroidinstaller/views.py | 21 ++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 9a6a1f58..ea7a2ce4 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -18,7 +18,7 @@ import webbrowser from pathlib import Path -import flet +import flet as ft from app_state import AppState from flet import ( AppBar, @@ -26,6 +26,7 @@ Column, Container, ElevatedButton, + CircleAvatar, FloatingActionButton, Icon, Image, @@ -217,4 +218,4 @@ def restart_process(e): page.add(app) -flet.app(target=main, assets_dir="assets") +ft.app(target=main, assets_dir="assets") diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index 6d021bee..cb901c72 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -23,6 +23,7 @@ AlertDialog, alignment, Checkbox, + Switch, Column, Container, Divider, @@ -251,10 +252,10 @@ def build(self): ], actions_alignment="end", ) - # checkbox to allow skipping unlocking the bootloader + # toggleswitch to allow skipping unlocking the bootloader def check_bootloader_unlocked(e): """Enable skipping unlocking the bootloader if selected.""" - if self.bootloader_checkbox.value: + if self.bootloader_switch.value: logger.info("Skipping bootloader unlocking.") self.state.steps = ( self.state.config.flash_recovery + self.state.config.install_os @@ -269,25 +270,25 @@ def check_bootloader_unlocked(e): ) self.state.num_total_steps = len(self.state.steps) - self.bootloader_checkbox = Checkbox( + self.bootloader_switch = Switch( label="Bootloader is already unlocked.", on_change=check_bootloader_unlocked, disabled=True, ) - # checkbox to enable advanced output - here it means show terminal input/output in tool - def check_advanced_box(e): + # switch to enable advanced output - here it means show terminal input/output in tool + def check_advanced_switch(e): """Check the box to enable advanced output.""" - if self.advanced_checkbox.value: + if self.advanced_switch.value: logger.info("Enable advanced output.") self.state.advanced = True else: logger.info("Disable advanced output.") self.state.advanced = False - self.advanced_checkbox = Checkbox( + self.advanced_switch = Switch( label="Advanced output", - on_change=check_advanced_box, + on_change=check_advanced_switch, disabled=False, ) @@ -347,7 +348,7 @@ def check_advanced_box(e): Column( [ self.device_detection_infobox, - Row([self.bootloader_checkbox, self.advanced_checkbox]), + Row([self.bootloader_switch, self.advanced_switch]), ] ), Row( @@ -421,7 +422,7 @@ def search_devices(self, e): # display success in the application if device_name: self.continue_button.disabled = False - self.bootloader_checkbox.disabled = False + self.bootloader_switch.disabled = False # overwrite the text field with the real name from the config self.device_name.value = f"{device_name} (code: {device_code})" self.device_name.color = colors.GREEN From 58b093b6d4c2bf0ab1fe197a8bcc84a30a1a48df Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 11:26:18 +0100 Subject: [PATCH 12/35] Add card control to requriements view --- openandroidinstaller/views.py | 69 +++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index cb901c72..af737da0 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -24,6 +24,7 @@ alignment, Checkbox, Switch, + Card, Column, Container, Divider, @@ -135,19 +136,24 @@ def enable_continue_button(e): label="The required android version is installed. (Or I know the risk of continuing)", on_change=enable_continue_button, ) - android_version_check = Column( - [ - Markdown( - f""" + android_version_check = Card( + Container( + content=Column( + [ + Markdown( + f""" #### Android Version {required_android_version}: Before following these instructions please ensure that the device is currently using Android {required_android_version} firmware. If the vendor provided multiple updates for that version, e.g. security updates, make sure you are on the latest! If your current installation is newer or older than Android {required_android_version}, please upgrade or downgrade to the required version before proceeding (guides can be found on the internet!). """ + ), + android_checkbox, + ] ), - android_checkbox, - ] + padding=10, + ) ) self.checkboxes.append(android_checkbox) self.right_view.controls.append(android_version_check) @@ -159,18 +165,23 @@ def enable_continue_button(e): label="The required firmware version is installed. (Or I know the risk of continuing)", on_change=enable_continue_button, ) - firmware_version_check = Column( - [ - Markdown( - f""" + firmware_version_check = Card( + Container( + content=Column( + [ + Markdown( + f""" #### Firmware Version {required_firmware_version}: Before following these instructions please ensure that the device is on firmware version {required_firmware_version}. To discern this, you can run the command `adb shell getprop ro.build.display.id` on the stock ROM. If the device is not on the specified version, please follow the instructions below to install it. """ + ), + firmware_checkbox, + ] ), - firmware_checkbox, - ] + padding=10, + ) ) self.checkboxes.append(firmware_checkbox) self.right_view.controls.append(firmware_version_check) @@ -180,16 +191,21 @@ def enable_continue_button(e): label="The battery level is over 80%.", on_change=enable_continue_button, ) - battery_version_check = Column( - [ - Markdown( - f""" + battery_version_check = Card( + Container( + content=Column( + [ + Markdown( + f""" #### Battery level over 80% Before continuing make sure your device battery level is above 80%. """ + ), + battery_checkbox, + ] ), - battery_checkbox, - ] + padding=10, + ), ) self.checkboxes.append(battery_checkbox) self.right_view.controls.append(battery_version_check) @@ -199,15 +215,20 @@ def enable_continue_button(e): label="No lock code or fingerprint lock enabled.", on_change=enable_continue_button, ) - lock_check = Column( - [ - Markdown( - f""" + lock_check = Card( + Container( + content=Column( + [ + Markdown( + f""" #### Disable all device lock codes and fingerprint locks. """ + ), + lock_checkbox, + ] ), - lock_checkbox, - ] + padding=10, + ), ) self.checkboxes.append(lock_checkbox) self.right_view.controls.append(lock_check) From b512ae1a542cc025d2ab03c15bf3778ac5b815bd Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 11:52:22 +0100 Subject: [PATCH 13/35] Update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f112fb99..9299ec1e 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ Every step in the config file corresponds to one view in the application. These - `allow_skip`: [OPTIONAL] boolean; If a skip button should be displayed to allow skipping this step. Can be useful when the bootloader is already unlocked. - `link`: [OPTIONAL] Link to use for the link button if type is `link_button_with_confirm`. +You can also use the `requirements` field in the yaml, to specify `firmware` or `android` version requirements. The user will then be prompted if these requirements are satisfied. + ### How to build the application for your platform The executables for the OpenAndroidInstaller are build with [pyinstaller](https://pyinstaller.org/en/stable/index.html). You can create builds for MacOS or Linux with `make build-app`. For Windows the paths need to be modified. For now, you can have a look [here](https://github.com/openandroidinstaller-dev/openandroidinstaller/blob/v0.1.2-alpha/.github/workflows/manual-build-windows.yml#L22) on how it's done. From edb354553a3df6895547f044bdf554b9d4d10837 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 11:56:22 +0100 Subject: [PATCH 14/35] fix tests for configs --- openandroidinstaller/installer_config.py | 2 +- tests/test_configs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 97a058a9..ffbec9f4 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -151,7 +151,7 @@ def validate_config(config: str) -> bool: "devicecode": str, }, schema.Optional("requirements"): { - schema.Optional("android"): str, + schema.Optional("android"): schema.Or(str, int), schema.Optional("firmware"): str, }, "steps": { diff --git a/tests/test_configs.py b/tests/test_configs.py index 868da495..fa1bda15 100644 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -13,7 +13,7 @@ [(path, True) for path in Path("openandroidinstaller/assets/configs").iterdir()], ) def test_config(config_path: Path, valid: bool): - """Test if the exisitng configs are valid.""" + """Test if the existing configs are valid.""" with open(config_path, "r") as stream: raw_config = yaml.safe_load(stream) assert valid == validate_config(raw_config) From 120e12bb95986c3bd003ac0507591f866080efd0 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 12:05:10 +0100 Subject: [PATCH 15/35] Add code coverage --- Makefile | 2 +- openandroidinstaller/openandroidinstaller.py | 1 - openandroidinstaller/views.py | 4 +- poetry.lock | 87 +++++++++++++++++++- pyproject.toml | 1 + 5 files changed, 90 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b8a2f1c8..3b0a8a34 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ lint: poetry run ruff openandroidinstaller/ --ignore E501 test: - poetry run pytest tests/ + poetry run pytest --cov=openandroidinstaller tests/ app: poetry run python openandroidinstaller/openandroidinstaller.py diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index ea7a2ce4..23599335 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -26,7 +26,6 @@ Column, Container, ElevatedButton, - CircleAvatar, FloatingActionButton, Icon, Image, diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index af737da0..917b2c70 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -196,7 +196,7 @@ def enable_continue_button(e): content=Column( [ Markdown( - f""" + """ #### Battery level over 80% Before continuing make sure your device battery level is above 80%. """ @@ -220,7 +220,7 @@ def enable_continue_button(e): content=Column( [ Markdown( - f""" + """ #### Disable all device lock codes and fingerprint locks. """ ), diff --git a/poetry.lock b/poetry.lock index c0a160fe..f89bfe4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -133,6 +133,20 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "flet" version = "0.1.63" @@ -473,6 +487,21 @@ tomli = ">=1.0.0" [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "pywin32-ctypes" version = "0.2.0" @@ -649,7 +678,7 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [metadata] lock-version = "1.1" python-versions = "<3.11,>=3.8" -content-hash = "84188bcea655ffb7b980c4a643a707061580e97d325c93132ae9c09e1127cdc2" +content-hash = "8ffe793af342bfc118f8a2d618c388fa23907c59a57a01629d6701f22cd791c5" [metadata.files] altgraph = [ @@ -871,6 +900,58 @@ contextlib2 = [ {file = "contextlib2-21.6.0-py2.py3-none-any.whl", hash = "sha256:3fbdb64466afd23abaf6c977627b75b6139a5a3e8ce38405c5b413aed7a0471f"}, {file = "contextlib2-21.6.0.tar.gz", hash = "sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869"}, ] +coverage = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] flet = [ {file = "flet-0.1.63-py3-none-any.whl", hash = "sha256:a37948114b130c35942d181b9df67c0c70381c72f3e3b7cf9d88aee662226de6"}, {file = "flet-0.1.63-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:5df068a8a4755c8d13fca7069a540469836bf8ca110d60ee951aad800dcdcda9"}, @@ -1333,6 +1414,10 @@ pytest = [ {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, ] +pytest-cov = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, diff --git a/pyproject.toml b/pyproject.toml index b08e7e05..e54d0fab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ pytest = "^7.1.3" regex = "^2022.9.13" schema = "^0.7.5" py7zr = "^0.20.0" +pytest-cov = "^4.0.0" [tool.poetry.dev-dependencies] From d0d86e11aefcf1ac6241904022708de99cf03ea8 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 12:17:39 +0100 Subject: [PATCH 16/35] more tests for installer-config --- Makefile | 2 ++ tests/conftest.py | 7 +++++++ tests/test_configs.py | 19 ++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3b0a8a34..57bbe86e 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,8 @@ lint: poetry run ruff openandroidinstaller/ --ignore E501 test: + poetry run black . + poetry run ruff openandroidinstaller/ --ignore E501 poetry run pytest --cov=openandroidinstaller tests/ app: diff --git a/tests/conftest.py b/tests/conftest.py index e69de29b..8a044977 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,7 @@ +import pytest +from pathlib import Path + + +@pytest.fixture +def config_path(): + return Path("openandroidinstaller/assets/configs") diff --git a/tests/test_configs.py b/tests/test_configs.py index fa1bda15..8558306e 100644 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -5,7 +5,7 @@ import pytest import yaml -from openandroidinstaller.installer_config import validate_config +from openandroidinstaller.installer_config import validate_config, _load_config @pytest.mark.parametrize( @@ -17,3 +17,20 @@ def test_config(config_path: Path, valid: bool): with open(config_path, "r") as stream: raw_config = yaml.safe_load(stream) assert valid == validate_config(raw_config) + + +def test_load_config_valid(config_path): + """Test if a valid config can be loaded.""" + config = _load_config(device_code="sargo", config_path=config_path) + + # assert some properties of the config + assert config + assert config.metadata.get("devicecode") == "sargo" + + +def test_load_config_notfound(config_path): + """Test if the function properly escalates when a config is not found.""" + config = _load_config(device_code="nothing", config_path=config_path) + + # assert some properties of the config + assert config == None From 7f40afbbf3478626cc918c1989c52de4e91a58ab Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 12:23:59 +0100 Subject: [PATCH 17/35] Move licencse header around --- openandroidinstaller/tool_utils.py | 6 +++++- openandroidinstaller/views.py | 5 ----- tests/conftest.py | 13 +++++++++++++ tests/test_configs.py | 13 +++++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/openandroidinstaller/tool_utils.py b/openandroidinstaller/tool_utils.py index 1f368372..63eee651 100644 --- a/openandroidinstaller/tool_utils.py +++ b/openandroidinstaller/tool_utils.py @@ -260,6 +260,10 @@ def heimdall_flash_recovery(bin_path: Path, recovery: str) -> bool: def search_device(platform: str, bin_path: Path) -> Optional[str]: """Search for a connected device.""" logger.info(f"Search devices on {platform} with {bin_path}...") + # map some detected device codes to their real code. + device_code_mapping = { + "C6603": "yuga", + } try: # read device properties if platform in ("linux", "darwin"): @@ -291,7 +295,7 @@ def search_device(platform: str, bin_path: Path) -> Optional[str]: raise Exception(f"Unknown platform {platform}.") device_code = output.split("[")[-1].strip()[:-1].strip() logger.info(device_code) - return device_code + return device_code_mapping.get(device_code, device_code) except CalledProcessError: logger.error("Failed to detect a device.") return None diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py index 917b2c70..4f4da7ce 100644 --- a/openandroidinstaller/views.py +++ b/openandroidinstaller/views.py @@ -402,10 +402,6 @@ def close_developer_options_dlg(self, e): def search_devices(self, e): """Search the device when the button is clicked.""" - # map some detected device codes to their real code. - device_code_mapping = { - "C6603": "yuga", - } # search the device if self.state.test: # this only happens for testing @@ -418,7 +414,6 @@ def search_devices(self, e): platform=self.state.platform, bin_path=self.state.bin_path ) if device_code: - device_code = device_code_mapping.get(device_code, device_code) self.device_name.value = device_code self.device_name.color = colors.BLACK else: diff --git a/tests/conftest.py b/tests/conftest.py index 8a044977..2bf0c8d9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,16 @@ +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + import pytest from pathlib import Path diff --git a/tests/test_configs.py b/tests/test_configs.py index 8558306e..2230e7d3 100644 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -1,5 +1,18 @@ """Test existing config files for schema.""" +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + from pathlib import Path import pytest From 10b8f2da6ed3c4090539001a4b38515acec94dbb Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 13:07:32 +0100 Subject: [PATCH 18/35] test search device function in tooling --- poetry.lock | 20 +++++++++++++- pyproject.toml | 1 + tests/test_tool_utils.py | 59 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/test_tool_utils.py diff --git a/poetry.lock b/poetry.lock index f89bfe4c..88d8634c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -502,6 +502,20 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pywin32-ctypes" version = "0.2.0" @@ -678,7 +692,7 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [metadata] lock-version = "1.1" python-versions = "<3.11,>=3.8" -content-hash = "8ffe793af342bfc118f8a2d618c388fa23907c59a57a01629d6701f22cd791c5" +content-hash = "1d3e95d25809034566b293d8053213187f77bb5c0fadb31c1bc07bc097d9143b" [metadata.files] altgraph = [ @@ -1418,6 +1432,10 @@ pytest-cov = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] +pytest-mock = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, diff --git a/pyproject.toml b/pyproject.toml index e54d0fab..55272323 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ regex = "^2022.9.13" schema = "^0.7.5" py7zr = "^0.20.0" pytest-cov = "^4.0.0" +pytest-mock = "^3.10.0" [tool.poetry.dev-dependencies] diff --git a/tests/test_tool_utils.py b/tests/test_tool_utils.py new file mode 100644 index 00000000..764876e0 --- /dev/null +++ b/tests/test_tool_utils.py @@ -0,0 +1,59 @@ +"""Test interactions with tools like adb and fastboot""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +from pathlib import Path +from subprocess import CalledProcessError + +from openandroidinstaller.tool_utils import search_device + + +def test_search_device_success(mocker): + """Test if search works fine.""" + mocker.patch( + "openandroidinstaller.tool_utils.check_output", + return_value=b"[ro.product.device]: [sargo]", + ) + + # test linux + device_code = search_device( + platform="linux", bin_path=Path("openandroidinstaller/bin/") + ) + + assert device_code == "sargo" + + # test windows + device_code = search_device( + platform="windows", bin_path=Path("openandroidinstaller/bin/") + ) + + assert device_code == "sargo" + + +def test_search_device_failure(mocker): + """Test if search failure is escalated properly.""" + + def patched_check_output(*args, **kwargs): + raise CalledProcessError(returncode=1, cmd="search device failed") + + mocker.patch( + "openandroidinstaller.tool_utils.check_output", + patched_check_output, + ) + + device_code = search_device( + platform="linux", bin_path=Path("openandroidinstaller/bin/") + ) + + assert device_code == None From 3db2fc47da180397a230f1dbc166b66506ecc285 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 14:27:49 +0100 Subject: [PATCH 19/35] Refactor views --- openandroidinstaller/openandroidinstaller.py | 6 +- .../{tool_utils.py => tooling.py} | 0 openandroidinstaller/views.py | 858 ------------------ openandroidinstaller/views/__init__.py | 6 + openandroidinstaller/views/base.py | 45 + .../views/requirements_view.py | 186 ++++ openandroidinstaller/views/select_view.py | 207 +++++ openandroidinstaller/views/start_view.py | 251 +++++ openandroidinstaller/views/step_view.py | 260 ++++++ openandroidinstaller/views/success_view.py | 49 + tests/{test_tool_utils.py => test_tooling.py} | 2 +- 11 files changed, 1008 insertions(+), 862 deletions(-) rename openandroidinstaller/{tool_utils.py => tooling.py} (100%) delete mode 100644 openandroidinstaller/views.py create mode 100644 openandroidinstaller/views/__init__.py create mode 100644 openandroidinstaller/views/base.py create mode 100644 openandroidinstaller/views/requirements_view.py create mode 100644 openandroidinstaller/views/select_view.py create mode 100644 openandroidinstaller/views/start_view.py create mode 100644 openandroidinstaller/views/step_view.py create mode 100644 openandroidinstaller/views/success_view.py rename tests/{test_tool_utils.py => test_tooling.py} (96%) diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 23599335..4dfefa8e 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -38,8 +38,8 @@ icons, ) from loguru import logger -from views import SelectFilesView, StepView, SuccessView, WelcomeView, RequirementsView -from tool_utils import run_command +from views import SelectFilesView, StepView, SuccessView, StartView, RequirementsView +from tooling import run_command # where to write the logs logger.add("openandroidinstaller.log") @@ -75,7 +75,7 @@ def __init__(self): self.view = Column(expand=True, width=1200) # create default starter views - welcome_view = WelcomeView( + welcome_view = StartView( on_confirm=self.confirm, state=self.state, ) diff --git a/openandroidinstaller/tool_utils.py b/openandroidinstaller/tooling.py similarity index 100% rename from openandroidinstaller/tool_utils.py rename to openandroidinstaller/tooling.py diff --git a/openandroidinstaller/views.py b/openandroidinstaller/views.py deleted file mode 100644 index 4f4da7ce..00000000 --- a/openandroidinstaller/views.py +++ /dev/null @@ -1,858 +0,0 @@ -"""This file contains the flet views of the application.""" - -# This file is part of OpenAndroidInstaller. -# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of -# the GNU General Public License as published by the Free Software Foundation, -# either version 3 of the License, or (at your option) any later version. - -# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. -# If not, see .""" -# Author: Tobias Sterbak - -import webbrowser -from time import sleep -from typing import Callable -from pathlib import Path -from loguru import logger - -from flet import ( - AlertDialog, - alignment, - Checkbox, - Switch, - Card, - Column, - Container, - Divider, - ElevatedButton, - FilePicker, - FilePickerResultEvent, - FilledButton, - Image, - Markdown, - ProgressBar, - Row, - Text, - TextButton, - TextField, - UserControl, - VerticalDivider, - colors, - icons, -) - -from app_state import AppState -from installer_config import Step -from tool_utils import ( - adb_reboot, - adb_reboot_bootloader, - adb_reboot_download, - adb_sideload, - adb_twrp_wipe_and_install, - fastboot_flash_recovery, - fastboot_oem_unlock, - fastboot_reboot, - fastboot_unlock, - fastboot_unlock_with_code, - heimdall_flash_recovery, - search_device, -) -from utils import get_download_link, image_recovery_works_with_device -from widgets import call_button, confirm_button, get_title, link_button - - -class BaseView(UserControl): - def __init__(self, state: AppState, image: str = "placeholder.png"): - super().__init__() - self.state = state - # right part of the display, add content here. - self.right_view = Column(expand=True) - # left part of the display: used for displaying the images - self.left_view = Column( - width=600, - controls=[Image(src=f"/assets/imgs/{image}")], - expand=True, - horizontal_alignment="center", - ) - # main view row - self.view = Row( - [self.left_view, VerticalDivider(), self.right_view], - alignment="spaceEvenly", - ) - - -class RequirementsView(BaseView): - """View to display requirements and ask for confirmation.""" - - def __init__( - self, - state: AppState, - on_confirm: Callable, - ): - super().__init__(state=state, image="requirements-default.png") - self.on_confirm = on_confirm - - def build(self): - self.continue_button = ElevatedButton( - "Continue", - on_click=self.on_confirm, - icon=icons.NEXT_PLAN_OUTLINED, - disabled=True, - expand=True, - ) - - # build up the main view - self.right_view.controls.extend( - [ - get_title("Check the Requirements"), - Text( - "Before continuing you need to check some requirements to progress. Please read the instructions and check the boxes if everything is fine." - ), - Divider(), - ] - ) - self.checkboxes = [] - - def enable_continue_button(e): - """Enable the continue button if all checkboxes are ticked.""" - for checkbox in self.checkboxes: - if not checkbox.value: - self.continue_button.disabled = True - return - logger.info("All requirements ticked. Allow to continue") - self.continue_button.disabled = False - self.right_view.update() - - # check if there are additional requirements given in the config - if self.state.config.requirements: - # android version - required_android_version = self.state.config.requirements.get("android") - if required_android_version: - android_checkbox = Checkbox( - label="The required android version is installed. (Or I know the risk of continuing)", - on_change=enable_continue_button, - ) - android_version_check = Card( - Container( - content=Column( - [ - Markdown( - f""" -#### Android Version {required_android_version}: -Before following these instructions please ensure that the device is currently using Android {required_android_version} firmware. -If the vendor provided multiple updates for that version, e.g. security updates, make sure you are on the latest! -If your current installation is newer or older than Android {required_android_version}, please upgrade or downgrade to the required -version before proceeding (guides can be found on the internet!). - """ - ), - android_checkbox, - ] - ), - padding=10, - ) - ) - self.checkboxes.append(android_checkbox) - self.right_view.controls.append(android_version_check) - - # firmware version - required_firmware_version = self.state.config.requirements.get("firmware") - if required_firmware_version: - firmware_checkbox = Checkbox( - label="The required firmware version is installed. (Or I know the risk of continuing)", - on_change=enable_continue_button, - ) - firmware_version_check = Card( - Container( - content=Column( - [ - Markdown( - f""" -#### Firmware Version {required_firmware_version}: -Before following these instructions please ensure that the device is on firmware version {required_firmware_version}. -To discern this, you can run the command `adb shell getprop ro.build.display.id` on the stock ROM. -If the device is not on the specified version, please follow the instructions below to install it. - """ - ), - firmware_checkbox, - ] - ), - padding=10, - ) - ) - self.checkboxes.append(firmware_checkbox) - self.right_view.controls.append(firmware_version_check) - - # default requirements: battery level - battery_checkbox = Checkbox( - label="The battery level is over 80%.", - on_change=enable_continue_button, - ) - battery_version_check = Card( - Container( - content=Column( - [ - Markdown( - """ -#### Battery level over 80% -Before continuing make sure your device battery level is above 80%. - """ - ), - battery_checkbox, - ] - ), - padding=10, - ), - ) - self.checkboxes.append(battery_checkbox) - self.right_view.controls.append(battery_version_check) - - # default requirement: disable lock code and fingerprint - lock_checkbox = Checkbox( - label="No lock code or fingerprint lock enabled.", - on_change=enable_continue_button, - ) - lock_check = Card( - Container( - content=Column( - [ - Markdown( - """ -#### Disable all device lock codes and fingerprint locks. - """ - ), - lock_checkbox, - ] - ), - padding=10, - ), - ) - self.checkboxes.append(lock_checkbox) - self.right_view.controls.append(lock_check) - - # add the final confirm and continue button - self.right_view.controls.append(Row([self.continue_button], alignment="center")) - return self.view - - -class WelcomeView(BaseView): - def __init__( - self, - state: AppState, - on_confirm: Callable, - ): - super().__init__(state=state, image="connect-to-usb.png") - self.on_confirm = on_confirm - - def build(self): - self.continue_button = ElevatedButton( - "Continue", - on_click=self.on_confirm, - icon=icons.NEXT_PLAN_OUTLINED, - disabled=True, - expand=True, - ) - - # dialog box to help with developer options - self.dlg_help_developer_options = AlertDialog( - modal=True, - title=Text("How to enable developer options and OEM unlocking"), - content=Markdown( - """ -To do this, tap seven times on the build number in the 'System'- or 'About the phone'-Menu in Settings. You can also use the phones own search to look for `build number`. -Then go back to the main menu and look for 'developer options'. You can also search for it in your phone. -When you are in developer options, toggle OEM unlocking and USB-Debugging. If your phone is already connected to your PC, a pop-up might appear. Allow USB debugging in the pop-up on your phone. -Now you are ready to continue. -""" - ), - actions=[ - TextButton("Close", on_click=self.close_developer_options_dlg), - ], - actions_alignment="end", - ) - # toggleswitch to allow skipping unlocking the bootloader - def check_bootloader_unlocked(e): - """Enable skipping unlocking the bootloader if selected.""" - if self.bootloader_switch.value: - logger.info("Skipping bootloader unlocking.") - self.state.steps = ( - self.state.config.flash_recovery + self.state.config.install_os - ) - self.state.num_total_steps = len(self.state.steps) - else: - logger.info("Enabled unlocking the bootloader again.") - self.state.steps = ( - self.state.config.unlock_bootloader - + self.state.config.flash_recovery - + self.state.config.install_os - ) - self.state.num_total_steps = len(self.state.steps) - - self.bootloader_switch = Switch( - label="Bootloader is already unlocked.", - on_change=check_bootloader_unlocked, - disabled=True, - ) - - # switch to enable advanced output - here it means show terminal input/output in tool - def check_advanced_switch(e): - """Check the box to enable advanced output.""" - if self.advanced_switch.value: - logger.info("Enable advanced output.") - self.state.advanced = True - else: - logger.info("Disable advanced output.") - self.state.advanced = False - - self.advanced_switch = Switch( - label="Advanced output", - on_change=check_advanced_switch, - disabled=False, - ) - - # inform the user about the device detection - self.device_name = Text("", weight="bold") - self.device_detection_infobox = Row( - [Text("Detected device:"), self.device_name] - ) - - # build up the main view - self.right_view.controls.extend( - [ - get_title("Welcome to the OpenAndroidInstaller!"), - Text( - "We will walk you through the installation process nice and easy." - ), - Divider(), - Markdown( - """ -Before you continue, make sure -- your devices is on the latest system update. -- you have a backup of all your important data, since this procedure will **erase all data from the phone**. -- to not store the backup not the phone! - -Please note, that vendor specific back-ups will most likely not work on LineageOS! - """ - ), - Divider(), - Markdown( - """ -To get started you need to -- **enable developer options** on your device -- and then **enable USB debugging** and **OEM unlocking** in the developer options. - """ - ), - Row( - [ - ElevatedButton( - "How do I enable developer options?", - on_click=self.open_developer_options_dlg, - expand=True, - tooltip="Get help to enable developer options and OEM unlocking.", - ) - ] - ), - Divider(), - Markdown( - """ -Now -- **connect your device to this computer via USB** and -- **allow USB debugging in the pop-up on your phone**. -- Then **press the button 'Search device'**. -When everything works correctly you should see your device name here and you can continue. - """ - ), - Divider(), - Column( - [ - self.device_detection_infobox, - Row([self.bootloader_switch, self.advanced_switch]), - ] - ), - Row( - [ - FilledButton( - "Search device", - on_click=self.search_devices, - icon=icons.PHONE_ANDROID, - expand=True, - tooltip="Search for a connected device.", - ), - self.continue_button, - ], - alignment="center", - ), - ] - ) - return self.view - - def open_developer_options_dlg(self, e): - """Open the dialog for help to developer mode.""" - self.page.dialog = self.dlg_help_developer_options - self.dlg_help_developer_options.open = True - self.page.update() - - def close_developer_options_dlg(self, e): - """Close the dialog for help to developer mode.""" - self.dlg_help_developer_options.open = False - self.page.update() - - def search_devices(self, e): - """Search the device when the button is clicked.""" - # search the device - if self.state.test: - # this only happens for testing - device_code = self.state.test_config - logger.info( - f"Running search in development mode and loading config {device_code}.yaml." - ) - else: - device_code = search_device( - platform=self.state.platform, bin_path=self.state.bin_path - ) - if device_code: - self.device_name.value = device_code - self.device_name.color = colors.BLACK - else: - logger.info("No device detected! Connect to USB and try again.") - self.device_name.value = ( - "No device detected! Connect to USB and try again." - ) - self.device_name.color = colors.RED - - # load the config, if a device is detected - if device_code: - self.device_name.value = device_code - # load config from file - self.state.load_config(device_code) - if self.state.config: - device_name = self.state.config.metadata.get( - "devicename", "No device name in config." - ) - else: - device_name = None - - # display success in the application - if device_name: - self.continue_button.disabled = False - self.bootloader_switch.disabled = False - # overwrite the text field with the real name from the config - self.device_name.value = f"{device_name} (code: {device_code})" - self.device_name.color = colors.GREEN - else: - # failed to load config - logger.error(f"Failed to load config for {device_code}.") - self.device_name.value = ( - f"Failed to load config for device with code {device_code}." - ) - self.device_name.color = colors.RED - self.view.update() - - -class SelectFilesView(BaseView): - def __init__( - self, - state: AppState, - on_confirm: Callable, - ): - super().__init__(state=state) - self.on_confirm = on_confirm - - def build(self): - self.download_link = get_download_link( - self.state.config.metadata.get("devicecode", "ERROR") - ) - # initialize file pickers - self.pick_image_dialog = FilePicker(on_result=self.pick_image_result) - self.pick_recovery_dialog = FilePicker(on_result=self.pick_recovery_result) - self.selected_image = Text("Selected image: ") - self.selected_recovery = Text("Selected recovery: ") - - # initialize and manage button state. - self.confirm_button = confirm_button(self.on_confirm) - self.confirm_button.disabled = True - self.pick_recovery_dialog.on_result = self.enable_button_if_ready - self.pick_image_dialog.on_result = self.enable_button_if_ready - - # attach hidden dialogues - self.right_view.controls.append(self.pick_image_dialog) - self.right_view.controls.append(self.pick_recovery_dialog) - # add title and progressbar - self.right_view.controls.append(get_title("Pick image and recovery files:")) - self.right_view.controls.append(self.state.progressbar) - # text row to show infos during the process - self.info_field = Row() - # if there is an available download, show the button to the page - if self.download_link: - self.right_view.controls.append( - Column( - [ - Text( - "You can bring your own image and recovery or you download the officially supported image file for your device here:" - ), - Row( - [ - ElevatedButton( - "Download LineageOS image", - icon=icons.DOWNLOAD_OUTLINED, - on_click=lambda _: webbrowser.open( - self.download_link - ), - expand=True, - ), - ElevatedButton( - "Download TWRP recovery", - icon=icons.DOWNLOAD_OUTLINED, - on_click=lambda _: webbrowser.open( - f"https://dl.twrp.me/{self.state.config.metadata.get('devicecode')}" - ), - expand=True, - ), - ] - ), - Markdown( - f""" -The image file should look something like `lineage-19.1-20221101-nightly-{self.state.config.metadata.get('devicecode')}-signed.zip` -and the recovery like `twrp-3.6.2_9-0-{self.state.config.metadata.get('devicecode')}.img`. Note that this tool only supports TWRP recoveries for now. -""" - ), - Divider(), - ] - ) - ) - # attach the controls for uploading image and recovery - self.right_view.controls.extend( - [ - Text( - "Now select the operating system image and recovery (note, that only TWRP recoveries are supported):" - ), - Row( - [ - ElevatedButton( - "Pick image file", - icon=icons.UPLOAD_FILE, - on_click=lambda _: self.pick_image_dialog.pick_files( - allow_multiple=False, - file_type="custom", - allowed_extensions=["zip"], - ), - expand=True, - ), - ] - ), - self.selected_image, - Row( - [ - ElevatedButton( - "Pick recovery file", - icon=icons.UPLOAD_FILE, - on_click=lambda _: self.pick_recovery_dialog.pick_files( - allow_multiple=False, - file_type="custom", - allowed_extensions=["img"], - ), - expand=True, - ), - ] - ), - self.selected_recovery, - Divider(), - Text( - "If you selected both files and they work for your device you can continue." - ), - self.info_field, - Row([self.confirm_button]), - ] - ) - return self.view - - def pick_image_result(self, e: FilePickerResultEvent): - path = ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" - self.selected_image.value = ( - self.selected_image.value.split(":")[0] + f": {path}" - ) - if e.files: - self.image_path = e.files[0].path - self.state.image_path = e.files[0].path - logger.info(f"Selected image from {self.image_path}") - else: - logger.info("No image selected.") - self.selected_image.update() - - def pick_recovery_result(self, e: FilePickerResultEvent): - path = ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" - self.selected_recovery.value = ( - self.selected_recovery.value.split(":")[0] + f": {path}" - ) - if e.files: - self.recovery_path = e.files[0].path - self.state.recovery_path = e.files[0].path - logger.info(f"Selected recovery from {self.recovery_path}") - else: - logger.info("No image selected.") - self.selected_recovery.update() - - def enable_button_if_ready(self, e): - """Enable the confirm button if both files have been selected.""" - if (".zip" in self.selected_image.value) and ( - ".img" in self.selected_recovery.value - ): - if not image_recovery_works_with_device( - device_code=self.state.config.metadata.get("devicecode"), - image_path=self.state.image_path, - recovery_path=self.state.recovery_path, - ): - # if image and recovery work for device allow to move on, otherwise display message - logger.error( - "Image and recovery don't work with the device. Please select different ones." - ) - self.info_field.controls = [ - Text( - "Image and recovery don't work with the device. Please select different ones." - ) - ] - self.right_view.update() - return - logger.info("Image and recovery work with the device. You can continue.") - self.info_field.controls = [] - self.confirm_button.disabled = False - self.right_view.update() - else: - self.confirm_button.disabled = True - - -class StepView(BaseView): - def __init__( - self, - step: Step, - state: AppState, - on_confirm: Callable, - ): - super().__init__(state=state, image=step.img) - self.step = step - self.on_confirm = on_confirm - - # text input - self.inputtext = TextField( - hint_text="your unlock code", expand=False - ) # textfield for the unlock code - - def build(self): - """Create the content of a view from step.""" - # text box for terminal output - self.terminal_box = Container( - content=Column(scroll="auto", expand=True), - margin=10, - padding=10, - alignment=alignment.top_left, - bgcolor=colors.BLACK38, - height=300, - border_radius=2, - expand=True, - ) - # main controls - self.right_view.controls = [ - get_title(f"{self.step.title}"), - self.state.progressbar, - Text(f"{self.step.content}"), - ] - # basic view depending on step.type - logger.info(f"Starting step of type {self.step.type}.") - self.confirm_button = confirm_button(self.on_confirm) - if self.step.type == "confirm_button": - self.right_view.controls.append(Row([self.confirm_button])) - elif self.step.type == "call_button": - self.confirm_button.disabled = True - self.call_button = call_button( - self.call_to_phone, command=self.step.command - ) - self.right_view.controls.append( - Row([self.call_button, self.confirm_button]), - ) - # add terminal box if enabled - if self.state.advanced: - self.right_view.controls.append(Row([self.terminal_box])) - elif self.step.type == "call_button_with_input": - self.confirm_button.disabled = True - self.call_button = call_button( - self.call_to_phone, command=self.step.command - ) - self.right_view.controls.extend( - [self.inputtext, Row([self.call_button, self.confirm_button])] - ) - elif self.step.type == "link_button_with_confirm": - self.right_view.controls.extend( - [Row([link_button(self.step.link, "Open Link"), self.confirm_button])] - ) - - elif self.step.type != "text": - logger.error(f"Unknown step type: {self.step.type}") - raise Exception(f"Unknown step type: {self.step.type}") - - # if skipping is allowed add a button to the view - if self.step.allow_skip or self.state.test: - self.right_view.controls.append( - Row( - [ - Text("Do you want to skip?"), - ElevatedButton( - "Skip", - on_click=self.on_confirm, - icon=icons.NEXT_PLAN_OUTLINED, - expand=True, - ), - ] - ) - ) - return self.view - - def call_to_phone(self, e, command: str): - """ - Run the command given on the phone. - - Some parts of the command are changed by placeholders. - """ - # disable the call button while the command is running - self.call_button.disabled = True - # reset terminal output - if self.state.advanced: - self.terminal_box.content.controls = [] - # display a progress bar to show something is happening - self.right_view.controls.append( - Row( - [ - ProgressBar( - width=600, color="#00d886", bgcolor="#eeeeee", bar_height=16 - ) - ], - alignment="center", - ), - ) - self.right_view.update() - - cmd_mapping = { - "adb_reboot": adb_reboot, - "adb_reboot_bootloader": adb_reboot_bootloader, - "adb_reboot_download": adb_reboot_download, - "fastboot_unlock": fastboot_unlock, - "fastboot_oem_unlock": fastboot_oem_unlock, - "fastboot_reboot": fastboot_reboot, - } - - # run the right command - if command in cmd_mapping.keys(): - for line in cmd_mapping.get(command)(bin_path=self.state.bin_path): - if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line - elif command == "adb_sideload": - for line in adb_sideload( - bin_path=self.state.bin_path, target=self.state.image_path - ): - if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line - elif command == "adb_twrp_wipe_and_install": - for line in adb_twrp_wipe_and_install( - bin_path=self.state.bin_path, - target=self.state.image_path, - config_path=self.state.config_path.joinpath( - Path(f"{self.state.config.metadata.get('devicecode')}.yaml") - ), - ): - if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line - elif command == "fastboot_flash_recovery": - for line in fastboot_flash_recovery( - bin_path=self.state.bin_path, recovery=self.state.recovery_path - ): - if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line - elif command == "fastboot_unlock_with_code": - for line in fastboot_unlock_with_code( - bin_path=self.state.bin_path, unlock_code=self.inputtext.value - ): - if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line - elif command == "heimdall_flash_recovery": - for line in heimdall_flash_recovery( - bin_path=self.state.bin_path, recovery=self.state.recovery_path - ): - if self.state.advanced and (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line - else: - logger.error(f"Unknown command type: {command}. Stopping.") - raise Exception(f"Unknown command type: {command}. Stopping.") - - # update the view accordingly - if not success: - # enable call button to retry - self.call_button.disabled = False - # pop the progress bar - self.right_view.controls.pop() - # also remove the last error text if it happened - if isinstance(self.right_view.controls[-1], Text): - self.right_view.controls.pop() - self.right_view.controls.append( - Text( - f"Command {command} failed! Try again or make sure everything is setup correctly." - ) - ) - else: - sleep(5) # wait to make sure everything is fine - logger.success(f"Command {command} run successfully. Allow to continue.") - # pop the progress bar - self.right_view.controls.pop() - # emable the confirm buton and disable the call button - self.confirm_button.disabled = False - self.call_button.disabled = True - self.view.update() - - -class SuccessView(BaseView): - def __init__(self, state: AppState): - super().__init__(state=state, image="success.png") - - def build( - self, - ): - self.right_view.controls = [ - get_title("Installation completed successfully!"), - self.state.progressbar, - Text("Now your devices boots into the new OS. Have fun with it!"), - Row( - [ - ElevatedButton( - "Finish and close", - expand=True, - on_click=lambda _: self.page.window_close(), - ) - ] - ), - ] - return self.view diff --git a/openandroidinstaller/views/__init__.py b/openandroidinstaller/views/__init__.py new file mode 100644 index 00000000..58a1f77e --- /dev/null +++ b/openandroidinstaller/views/__init__.py @@ -0,0 +1,6 @@ +from .base import BaseView +from .start_view import StartView +from .requirements_view import RequirementsView +from .select_view import SelectFilesView +from .step_view import StepView +from .success_view import SuccessView \ No newline at end of file diff --git a/openandroidinstaller/views/base.py b/openandroidinstaller/views/base.py new file mode 100644 index 00000000..95d84558 --- /dev/null +++ b/openandroidinstaller/views/base.py @@ -0,0 +1,45 @@ +"""Contains the base class for views.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +from app_state import AppState +from loguru import logger + +from flet import ( + Column, + Image, + Row, + UserControl, + VerticalDivider, +) + + +class BaseView(UserControl): + def __init__(self, state: AppState, image: str = "placeholder.png"): + super().__init__() + self.state = state + # right part of the display, add content here. + self.right_view = Column(expand=True) + # left part of the display: used for displaying the images + self.left_view = Column( + width=600, + controls=[Image(src=f"/assets/imgs/{image}")], + expand=True, + horizontal_alignment="center", + ) + # main view row + self.view = Row( + [self.left_view, VerticalDivider(), self.right_view], + alignment="spaceEvenly", + ) \ No newline at end of file diff --git a/openandroidinstaller/views/requirements_view.py b/openandroidinstaller/views/requirements_view.py new file mode 100644 index 00000000..792b3eef --- /dev/null +++ b/openandroidinstaller/views/requirements_view.py @@ -0,0 +1,186 @@ +"""Contains the requirements view.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +from loguru import logger +from typing import Callable +from flet import ( + Checkbox, + Card, + Column, + Container, + Divider, + ElevatedButton, + Markdown, + Row, + Text, + icons, +) + +from views import BaseView +from app_state import AppState +from widgets import get_title + + +class RequirementsView(BaseView): + """View to display requirements and ask for confirmation.""" + + def __init__( + self, + state: AppState, + on_confirm: Callable, + ): + super().__init__(state=state, image="requirements-default.png") + self.on_confirm = on_confirm + + def build(self): + self.continue_button = ElevatedButton( + "Continue", + on_click=self.on_confirm, + icon=icons.NEXT_PLAN_OUTLINED, + disabled=True, + expand=True, + ) + + # build up the main view + self.right_view.controls.extend( + [ + get_title("Check the Requirements"), + Text( + "Before continuing you need to check some requirements to progress. Please read the instructions and check the boxes if everything is fine." + ), + Divider(), + ] + ) + self.checkboxes = [] + + def enable_continue_button(e): + """Enable the continue button if all checkboxes are ticked.""" + for checkbox in self.checkboxes: + if not checkbox.value: + self.continue_button.disabled = True + return + logger.info("All requirements ticked. Allow to continue") + self.continue_button.disabled = False + self.right_view.update() + + # check if there are additional requirements given in the config + if self.state.config.requirements: + # android version + required_android_version = self.state.config.requirements.get("android") + if required_android_version: + android_checkbox = Checkbox( + label="The required android version is installed. (Or I know the risk of continuing)", + on_change=enable_continue_button, + ) + android_version_check = Card( + Container( + content=Column( + [ + Markdown( + f""" +#### Android Version {required_android_version}: +Before following these instructions please ensure that the device is currently using Android {required_android_version} firmware. +If the vendor provided multiple updates for that version, e.g. security updates, make sure you are on the latest! +If your current installation is newer or older than Android {required_android_version}, please upgrade or downgrade to the required +version before proceeding (guides can be found on the internet!). + """ + ), + android_checkbox, + ] + ), + padding=10, + ) + ) + self.checkboxes.append(android_checkbox) + self.right_view.controls.append(android_version_check) + + # firmware version + required_firmware_version = self.state.config.requirements.get("firmware") + if required_firmware_version: + firmware_checkbox = Checkbox( + label="The required firmware version is installed. (Or I know the risk of continuing)", + on_change=enable_continue_button, + ) + firmware_version_check = Card( + Container( + content=Column( + [ + Markdown( + f""" +#### Firmware Version {required_firmware_version}: +Before following these instructions please ensure that the device is on firmware version {required_firmware_version}. +To discern this, you can run the command `adb shell getprop ro.build.display.id` on the stock ROM. +If the device is not on the specified version, please follow the instructions below to install it. + """ + ), + firmware_checkbox, + ] + ), + padding=10, + ) + ) + self.checkboxes.append(firmware_checkbox) + self.right_view.controls.append(firmware_version_check) + + # default requirements: battery level + battery_checkbox = Checkbox( + label="The battery level is over 80%.", + on_change=enable_continue_button, + ) + battery_version_check = Card( + Container( + content=Column( + [ + Markdown( + """ +#### Battery level over 80% +Before continuing make sure your device battery level is above 80%. + """ + ), + battery_checkbox, + ] + ), + padding=10, + ), + ) + self.checkboxes.append(battery_checkbox) + self.right_view.controls.append(battery_version_check) + + # default requirement: disable lock code and fingerprint + lock_checkbox = Checkbox( + label="No lock code or fingerprint lock enabled.", + on_change=enable_continue_button, + ) + lock_check = Card( + Container( + content=Column( + [ + Markdown( + """ +#### Disable all device lock codes and fingerprint locks. + """ + ), + lock_checkbox, + ] + ), + padding=10, + ), + ) + self.checkboxes.append(lock_checkbox) + self.right_view.controls.append(lock_check) + + # add the final confirm and continue button + self.right_view.controls.append(Row([self.continue_button], alignment="center")) + return self.view \ No newline at end of file diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py new file mode 100644 index 00000000..5b5313f3 --- /dev/null +++ b/openandroidinstaller/views/select_view.py @@ -0,0 +1,207 @@ +"""Contains the select files view.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +import webbrowser +from loguru import logger +from typing import Callable + +from flet import ( + Column, + Divider, + ElevatedButton, + Markdown, + Row, + Text, + icons, + FilePicker, + FilePickerResultEvent, +) + +from views import BaseView +from app_state import AppState +from widgets import get_title, confirm_button +from utils import get_download_link, image_recovery_works_with_device + + +class SelectFilesView(BaseView): + def __init__( + self, + state: AppState, + on_confirm: Callable, + ): + super().__init__(state=state) + self.on_confirm = on_confirm + + def build(self): + self.download_link = get_download_link( + self.state.config.metadata.get("devicecode", "ERROR") + ) + # initialize file pickers + self.pick_image_dialog = FilePicker(on_result=self.pick_image_result) + self.pick_recovery_dialog = FilePicker(on_result=self.pick_recovery_result) + self.selected_image = Text("Selected image: ") + self.selected_recovery = Text("Selected recovery: ") + + # initialize and manage button state. + self.confirm_button = confirm_button(self.on_confirm) + self.confirm_button.disabled = True + self.pick_recovery_dialog.on_result = self.enable_button_if_ready + self.pick_image_dialog.on_result = self.enable_button_if_ready + + # attach hidden dialogues + self.right_view.controls.append(self.pick_image_dialog) + self.right_view.controls.append(self.pick_recovery_dialog) + # add title and progressbar + self.right_view.controls.append(get_title("Pick image and recovery files:")) + self.right_view.controls.append(self.state.progressbar) + # text row to show infos during the process + self.info_field = Row() + # if there is an available download, show the button to the page + if self.download_link: + self.right_view.controls.append( + Column( + [ + Text( + "You can bring your own image and recovery or you download the officially supported image file for your device here:" + ), + Row( + [ + ElevatedButton( + "Download LineageOS image", + icon=icons.DOWNLOAD_OUTLINED, + on_click=lambda _: webbrowser.open( + self.download_link + ), + expand=True, + ), + ElevatedButton( + "Download TWRP recovery", + icon=icons.DOWNLOAD_OUTLINED, + on_click=lambda _: webbrowser.open( + f"https://dl.twrp.me/{self.state.config.metadata.get('devicecode')}" + ), + expand=True, + ), + ] + ), + Markdown( + f""" +The image file should look something like `lineage-19.1-20221101-nightly-{self.state.config.metadata.get('devicecode')}-signed.zip` +and the recovery like `twrp-3.6.2_9-0-{self.state.config.metadata.get('devicecode')}.img`. Note that this tool only supports TWRP recoveries for now. +""" + ), + Divider(), + ] + ) + ) + # attach the controls for uploading image and recovery + self.right_view.controls.extend( + [ + Text( + "Now select the operating system image and recovery (note, that only TWRP recoveries are supported):" + ), + Row( + [ + ElevatedButton( + "Pick image file", + icon=icons.UPLOAD_FILE, + on_click=lambda _: self.pick_image_dialog.pick_files( + allow_multiple=False, + file_type="custom", + allowed_extensions=["zip"], + ), + expand=True, + ), + ] + ), + self.selected_image, + Row( + [ + ElevatedButton( + "Pick recovery file", + icon=icons.UPLOAD_FILE, + on_click=lambda _: self.pick_recovery_dialog.pick_files( + allow_multiple=False, + file_type="custom", + allowed_extensions=["img"], + ), + expand=True, + ), + ] + ), + self.selected_recovery, + Divider(), + Text( + "If you selected both files and they work for your device you can continue." + ), + self.info_field, + Row([self.confirm_button]), + ] + ) + return self.view + + def pick_image_result(self, e: FilePickerResultEvent): + path = ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" + self.selected_image.value = ( + self.selected_image.value.split(":")[0] + f": {path}" + ) + if e.files: + self.image_path = e.files[0].path + self.state.image_path = e.files[0].path + logger.info(f"Selected image from {self.image_path}") + else: + logger.info("No image selected.") + self.selected_image.update() + + def pick_recovery_result(self, e: FilePickerResultEvent): + path = ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" + self.selected_recovery.value = ( + self.selected_recovery.value.split(":")[0] + f": {path}" + ) + if e.files: + self.recovery_path = e.files[0].path + self.state.recovery_path = e.files[0].path + logger.info(f"Selected recovery from {self.recovery_path}") + else: + logger.info("No image selected.") + self.selected_recovery.update() + + def enable_button_if_ready(self, e): + """Enable the confirm button if both files have been selected.""" + if (".zip" in self.selected_image.value) and ( + ".img" in self.selected_recovery.value + ): + if not image_recovery_works_with_device( + device_code=self.state.config.metadata.get("devicecode"), + image_path=self.state.image_path, + recovery_path=self.state.recovery_path, + ): + # if image and recovery work for device allow to move on, otherwise display message + logger.error( + "Image and recovery don't work with the device. Please select different ones." + ) + self.info_field.controls = [ + Text( + "Image and recovery don't work with the device. Please select different ones." + ) + ] + self.right_view.update() + return + logger.info("Image and recovery work with the device. You can continue.") + self.info_field.controls = [] + self.confirm_button.disabled = False + self.right_view.update() + else: + self.confirm_button.disabled = True \ No newline at end of file diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py new file mode 100644 index 00000000..cb3ea0fb --- /dev/null +++ b/openandroidinstaller/views/start_view.py @@ -0,0 +1,251 @@ +"""Contains the start view.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +from loguru import logger +from typing import Callable + +from flet import ( + AlertDialog, + Switch, + Column, + Divider, + ElevatedButton, + FilledButton, + Markdown, + Row, + Text, + TextButton, + colors, + icons, +) + +from views import BaseView +from app_state import AppState +from widgets import get_title +from tooling import search_device + + +class StartView(BaseView): + def __init__( + self, + state: AppState, + on_confirm: Callable, + ): + super().__init__(state=state, image="connect-to-usb.png") + self.on_confirm = on_confirm + + def build(self): + self.continue_button = ElevatedButton( + "Continue", + on_click=self.on_confirm, + icon=icons.NEXT_PLAN_OUTLINED, + disabled=True, + expand=True, + ) + + # dialog box to help with developer options + self.dlg_help_developer_options = AlertDialog( + modal=True, + title=Text("How to enable developer options and OEM unlocking"), + content=Markdown( + """ +To do this, tap seven times on the build number in the 'System'- or 'About the phone'-Menu in Settings. You can also use the phones own search to look for `build number`. +Then go back to the main menu and look for 'developer options'. You can also search for it in your phone. +When you are in developer options, toggle OEM unlocking and USB-Debugging. If your phone is already connected to your PC, a pop-up might appear. Allow USB debugging in the pop-up on your phone. +Now you are ready to continue. +""" + ), + actions=[ + TextButton("Close", on_click=self.close_developer_options_dlg), + ], + actions_alignment="end", + ) + # toggleswitch to allow skipping unlocking the bootloader + def check_bootloader_unlocked(e): + """Enable skipping unlocking the bootloader if selected.""" + if self.bootloader_switch.value: + logger.info("Skipping bootloader unlocking.") + self.state.steps = ( + self.state.config.flash_recovery + self.state.config.install_os + ) + self.state.num_total_steps = len(self.state.steps) + else: + logger.info("Enabled unlocking the bootloader again.") + self.state.steps = ( + self.state.config.unlock_bootloader + + self.state.config.flash_recovery + + self.state.config.install_os + ) + self.state.num_total_steps = len(self.state.steps) + + self.bootloader_switch = Switch( + label="Bootloader is already unlocked.", + on_change=check_bootloader_unlocked, + disabled=True, + ) + + # switch to enable advanced output - here it means show terminal input/output in tool + def check_advanced_switch(e): + """Check the box to enable advanced output.""" + if self.advanced_switch.value: + logger.info("Enable advanced output.") + self.state.advanced = True + else: + logger.info("Disable advanced output.") + self.state.advanced = False + + self.advanced_switch = Switch( + label="Advanced output", + on_change=check_advanced_switch, + disabled=False, + ) + + # inform the user about the device detection + self.device_name = Text("", weight="bold") + self.device_detection_infobox = Row( + [Text("Detected device:"), self.device_name] + ) + + # build up the main view + self.right_view.controls.extend( + [ + get_title("Welcome to the OpenAndroidInstaller!"), + Text( + "We will walk you through the installation process nice and easy." + ), + Divider(), + Markdown( + """ +Before you continue, make sure +- your devices is on the latest system update. +- you have a backup of all your important data, since this procedure will **erase all data from the phone**. +- to not store the backup not the phone! + +Please note, that vendor specific back-ups will most likely not work on LineageOS! + """ + ), + Divider(), + Markdown( + """ +To get started you need to +- **enable developer options** on your device +- and then **enable USB debugging** and **OEM unlocking** in the developer options. + """ + ), + Row( + [ + ElevatedButton( + "How do I enable developer options?", + on_click=self.open_developer_options_dlg, + expand=True, + tooltip="Get help to enable developer options and OEM unlocking.", + ) + ] + ), + Divider(), + Markdown( + """ +Now +- **connect your device to this computer via USB** and +- **allow USB debugging in the pop-up on your phone**. +- Then **press the button 'Search device'**. +When everything works correctly you should see your device name here and you can continue. + """ + ), + Divider(), + Column( + [ + self.device_detection_infobox, + Row([self.bootloader_switch, self.advanced_switch]), + ] + ), + Row( + [ + FilledButton( + "Search device", + on_click=self.search_devices, + icon=icons.PHONE_ANDROID, + expand=True, + tooltip="Search for a connected device.", + ), + self.continue_button, + ], + alignment="center", + ), + ] + ) + return self.view + + def open_developer_options_dlg(self, e): + """Open the dialog for help to developer mode.""" + self.page.dialog = self.dlg_help_developer_options + self.dlg_help_developer_options.open = True + self.page.update() + + def close_developer_options_dlg(self, e): + """Close the dialog for help to developer mode.""" + self.dlg_help_developer_options.open = False + self.page.update() + + def search_devices(self, e): + """Search the device when the button is clicked.""" + # search the device + if self.state.test: + # this only happens for testing + device_code = self.state.test_config + logger.info( + f"Running search in development mode and loading config {device_code}.yaml." + ) + else: + device_code = search_device( + platform=self.state.platform, bin_path=self.state.bin_path + ) + if device_code: + self.device_name.value = device_code + self.device_name.color = colors.BLACK + else: + logger.info("No device detected! Connect to USB and try again.") + self.device_name.value = ( + "No device detected! Connect to USB and try again." + ) + self.device_name.color = colors.RED + + # load the config, if a device is detected + if device_code: + self.device_name.value = device_code + # load config from file + self.state.load_config(device_code) + if self.state.config: + device_name = self.state.config.metadata.get( + "devicename", "No device name in config." + ) + else: + device_name = None + + # display success in the application + if device_name: + self.continue_button.disabled = False + self.bootloader_switch.disabled = False + # overwrite the text field with the real name from the config + self.device_name.value = f"{device_name} (code: {device_code})" + self.device_name.color = colors.GREEN + else: + # failed to load config + logger.error(f"Failed to load config for {device_code}.") + self.device_name.value = ( + f"Failed to load config for device with code {device_code}." + ) + self.device_name.color = colors.RED + self.view.update() \ No newline at end of file diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py new file mode 100644 index 00000000..534b2d2e --- /dev/null +++ b/openandroidinstaller/views/step_view.py @@ -0,0 +1,260 @@ +"""Contains the steps view.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +from loguru import logger +from time import sleep +from typing import Callable +from pathlib import Path + +from flet import ( + Column, + ElevatedButton, + Row, + Text, + icons, + TextField, + Container, + alignment, + colors, + ProgressBar, +) + +from views import BaseView +from installer_config import Step +from app_state import AppState +from tooling import ( + adb_reboot, + adb_reboot_bootloader, + adb_reboot_download, + adb_sideload, + adb_twrp_wipe_and_install, + fastboot_flash_recovery, + fastboot_oem_unlock, + fastboot_reboot, + fastboot_unlock, + fastboot_unlock_with_code, + heimdall_flash_recovery, +) +from widgets import call_button, confirm_button, get_title, link_button + + +class StepView(BaseView): + def __init__( + self, + step: Step, + state: AppState, + on_confirm: Callable, + ): + super().__init__(state=state, image=step.img) + self.step = step + self.on_confirm = on_confirm + + # text input + self.inputtext = TextField( + hint_text="your unlock code", expand=False + ) # textfield for the unlock code + + def build(self): + """Create the content of a view from step.""" + # text box for terminal output + self.terminal_box = Container( + content=Column(scroll="auto", expand=True), + margin=10, + padding=10, + alignment=alignment.top_left, + bgcolor=colors.BLACK38, + height=300, + border_radius=2, + expand=True, + ) + # main controls + self.right_view.controls = [ + get_title(f"{self.step.title}"), + self.state.progressbar, + Text(f"{self.step.content}"), + ] + # basic view depending on step.type + logger.info(f"Starting step of type {self.step.type}.") + self.confirm_button = confirm_button(self.on_confirm) + if self.step.type == "confirm_button": + self.right_view.controls.append(Row([self.confirm_button])) + elif self.step.type == "call_button": + self.confirm_button.disabled = True + self.call_button = call_button( + self.call_to_phone, command=self.step.command + ) + self.right_view.controls.append( + Row([self.call_button, self.confirm_button]), + ) + # add terminal box if enabled + if self.state.advanced: + self.right_view.controls.append(Row([self.terminal_box])) + elif self.step.type == "call_button_with_input": + self.confirm_button.disabled = True + self.call_button = call_button( + self.call_to_phone, command=self.step.command + ) + self.right_view.controls.extend( + [self.inputtext, Row([self.call_button, self.confirm_button])] + ) + elif self.step.type == "link_button_with_confirm": + self.right_view.controls.extend( + [Row([link_button(self.step.link, "Open Link"), self.confirm_button])] + ) + + elif self.step.type != "text": + logger.error(f"Unknown step type: {self.step.type}") + raise Exception(f"Unknown step type: {self.step.type}") + + # if skipping is allowed add a button to the view + if self.step.allow_skip or self.state.test: + self.right_view.controls.append( + Row( + [ + Text("Do you want to skip?"), + ElevatedButton( + "Skip", + on_click=self.on_confirm, + icon=icons.NEXT_PLAN_OUTLINED, + expand=True, + ), + ] + ) + ) + return self.view + + def call_to_phone(self, e, command: str): + """ + Run the command given on the phone. + + Some parts of the command are changed by placeholders. + """ + # disable the call button while the command is running + self.call_button.disabled = True + # reset terminal output + if self.state.advanced: + self.terminal_box.content.controls = [] + # display a progress bar to show something is happening + self.right_view.controls.append( + Row( + [ + ProgressBar( + width=600, color="#00d886", bgcolor="#eeeeee", bar_height=16 + ) + ], + alignment="center", + ), + ) + self.right_view.update() + + cmd_mapping = { + "adb_reboot": adb_reboot, + "adb_reboot_bootloader": adb_reboot_bootloader, + "adb_reboot_download": adb_reboot_download, + "fastboot_unlock": fastboot_unlock, + "fastboot_oem_unlock": fastboot_oem_unlock, + "fastboot_reboot": fastboot_reboot, + } + + # run the right command + if command in cmd_mapping.keys(): + for line in cmd_mapping.get(command)(bin_path=self.state.bin_path): + if self.state.advanced and (type(line) == str) and line.strip(): + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) + self.terminal_box.update() + success = line + elif command == "adb_sideload": + for line in adb_sideload( + bin_path=self.state.bin_path, target=self.state.image_path + ): + if self.state.advanced and (type(line) == str) and line.strip(): + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) + self.terminal_box.update() + success = line + elif command == "adb_twrp_wipe_and_install": + for line in adb_twrp_wipe_and_install( + bin_path=self.state.bin_path, + target=self.state.image_path, + config_path=self.state.config_path.joinpath( + Path(f"{self.state.config.metadata.get('devicecode')}.yaml") + ), + ): + if self.state.advanced and (type(line) == str) and line.strip(): + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) + self.terminal_box.update() + success = line + elif command == "fastboot_flash_recovery": + for line in fastboot_flash_recovery( + bin_path=self.state.bin_path, recovery=self.state.recovery_path + ): + if self.state.advanced and (type(line) == str) and line.strip(): + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) + self.terminal_box.update() + success = line + elif command == "fastboot_unlock_with_code": + for line in fastboot_unlock_with_code( + bin_path=self.state.bin_path, unlock_code=self.inputtext.value + ): + if self.state.advanced and (type(line) == str) and line.strip(): + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) + self.terminal_box.update() + success = line + elif command == "heimdall_flash_recovery": + for line in heimdall_flash_recovery( + bin_path=self.state.bin_path, recovery=self.state.recovery_path + ): + if self.state.advanced and (type(line) == str) and line.strip(): + self.terminal_box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) + self.terminal_box.update() + success = line + else: + logger.error(f"Unknown command type: {command}. Stopping.") + raise Exception(f"Unknown command type: {command}. Stopping.") + + # update the view accordingly + if not success: + # enable call button to retry + self.call_button.disabled = False + # pop the progress bar + self.right_view.controls.pop() + # also remove the last error text if it happened + if isinstance(self.right_view.controls[-1], Text): + self.right_view.controls.pop() + self.right_view.controls.append( + Text( + f"Command {command} failed! Try again or make sure everything is setup correctly." + ) + ) + else: + sleep(5) # wait to make sure everything is fine + logger.success(f"Command {command} run successfully. Allow to continue.") + # pop the progress bar + self.right_view.controls.pop() + # emable the confirm buton and disable the call button + self.confirm_button.disabled = False + self.call_button.disabled = True + self.view.update() diff --git a/openandroidinstaller/views/success_view.py b/openandroidinstaller/views/success_view.py new file mode 100644 index 00000000..335a8caa --- /dev/null +++ b/openandroidinstaller/views/success_view.py @@ -0,0 +1,49 @@ +"""Contains the final success view.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +from loguru import logger +from flet import ( + ElevatedButton, + Row, + Text, +) + +from views import BaseView +from app_state import AppState +from widgets import get_title + + +class SuccessView(BaseView): + def __init__(self, state: AppState): + super().__init__(state=state, image="success.png") + + def build( + self, + ): + self.right_view.controls = [ + get_title("Installation completed successfully!"), + self.state.progressbar, + Text("Now your devices boots into the new OS. Have fun with it!"), + Row( + [ + ElevatedButton( + "Finish and close", + expand=True, + on_click=lambda _: self.page.window_close(), + ) + ] + ), + ] + return self.view \ No newline at end of file diff --git a/tests/test_tool_utils.py b/tests/test_tooling.py similarity index 96% rename from tests/test_tool_utils.py rename to tests/test_tooling.py index 764876e0..d18c7a3d 100644 --- a/tests/test_tool_utils.py +++ b/tests/test_tooling.py @@ -16,7 +16,7 @@ from pathlib import Path from subprocess import CalledProcessError -from openandroidinstaller.tool_utils import search_device +from tooling import search_device def test_search_device_success(mocker): From 7764ee89f9c7d2305a93fbe030656c2c3fa2c4c2 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 14:47:54 +0100 Subject: [PATCH 20/35] WIP --- openandroidinstaller/views/__init__.py | 12 ++++++------ openandroidinstaller/views/base.py | 3 +-- openandroidinstaller/views/requirements_view.py | 2 +- openandroidinstaller/views/select_view.py | 2 +- openandroidinstaller/views/start_view.py | 2 +- openandroidinstaller/views/success_view.py | 8 ++++++-- tests/test_tooling.py | 6 +++--- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/openandroidinstaller/views/__init__.py b/openandroidinstaller/views/__init__.py index 58a1f77e..195cbe55 100644 --- a/openandroidinstaller/views/__init__.py +++ b/openandroidinstaller/views/__init__.py @@ -1,6 +1,6 @@ -from .base import BaseView -from .start_view import StartView -from .requirements_view import RequirementsView -from .select_view import SelectFilesView -from .step_view import StepView -from .success_view import SuccessView \ No newline at end of file +from .base import BaseView # noqa +from .start_view import StartView # noqa +from .requirements_view import RequirementsView # noqa +from .select_view import SelectFilesView # noqa +from .step_view import StepView # noqa +from .success_view import SuccessView # noqa diff --git a/openandroidinstaller/views/base.py b/openandroidinstaller/views/base.py index 95d84558..aa20f7ab 100644 --- a/openandroidinstaller/views/base.py +++ b/openandroidinstaller/views/base.py @@ -14,7 +14,6 @@ # Author: Tobias Sterbak from app_state import AppState -from loguru import logger from flet import ( Column, @@ -42,4 +41,4 @@ def __init__(self, state: AppState, image: str = "placeholder.png"): self.view = Row( [self.left_view, VerticalDivider(), self.right_view], alignment="spaceEvenly", - ) \ No newline at end of file + ) diff --git a/openandroidinstaller/views/requirements_view.py b/openandroidinstaller/views/requirements_view.py index 792b3eef..228694b0 100644 --- a/openandroidinstaller/views/requirements_view.py +++ b/openandroidinstaller/views/requirements_view.py @@ -183,4 +183,4 @@ def enable_continue_button(e): # add the final confirm and continue button self.right_view.controls.append(Row([self.continue_button], alignment="center")) - return self.view \ No newline at end of file + return self.view diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 5b5313f3..627ff864 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -204,4 +204,4 @@ def enable_button_if_ready(self, e): self.confirm_button.disabled = False self.right_view.update() else: - self.confirm_button.disabled = True \ No newline at end of file + self.confirm_button.disabled = True diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py index cb3ea0fb..84012e7f 100644 --- a/openandroidinstaller/views/start_view.py +++ b/openandroidinstaller/views/start_view.py @@ -248,4 +248,4 @@ def search_devices(self, e): f"Failed to load config for device with code {device_code}." ) self.device_name.color = colors.RED - self.view.update() \ No newline at end of file + self.view.update() diff --git a/openandroidinstaller/views/success_view.py b/openandroidinstaller/views/success_view.py index 335a8caa..e62b55a9 100644 --- a/openandroidinstaller/views/success_view.py +++ b/openandroidinstaller/views/success_view.py @@ -32,6 +32,10 @@ def __init__(self, state: AppState): def build( self, ): + def close_window(): + logger.success("Success! Close the window.") + self.page.window_close() + self.right_view.controls = [ get_title("Installation completed successfully!"), self.state.progressbar, @@ -41,9 +45,9 @@ def build( ElevatedButton( "Finish and close", expand=True, - on_click=lambda _: self.page.window_close(), + on_click=close_window, ) ] ), ] - return self.view \ No newline at end of file + return self.view diff --git a/tests/test_tooling.py b/tests/test_tooling.py index d18c7a3d..0a3225ce 100644 --- a/tests/test_tooling.py +++ b/tests/test_tooling.py @@ -16,13 +16,13 @@ from pathlib import Path from subprocess import CalledProcessError -from tooling import search_device +from openandroidinstaller.tooling import search_device def test_search_device_success(mocker): """Test if search works fine.""" mocker.patch( - "openandroidinstaller.tool_utils.check_output", + "openandroidinstaller.tooling.check_output", return_value=b"[ro.product.device]: [sargo]", ) @@ -48,7 +48,7 @@ def patched_check_output(*args, **kwargs): raise CalledProcessError(returncode=1, cmd="search device failed") mocker.patch( - "openandroidinstaller.tool_utils.check_output", + "openandroidinstaller.tooling.check_output", patched_check_output, ) From ef7d0c3a6dd933134724c2f871a22e9286ce9e45 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Fri, 25 Nov 2022 17:19:32 +0100 Subject: [PATCH 21/35] Let advanced view toggle in every step independently --- openandroidinstaller/views/start_view.py | 18 +---- openandroidinstaller/views/step_view.py | 96 ++++++++++++++++-------- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py index 84012e7f..a3688344 100644 --- a/openandroidinstaller/views/start_view.py +++ b/openandroidinstaller/views/start_view.py @@ -96,22 +96,6 @@ def check_bootloader_unlocked(e): disabled=True, ) - # switch to enable advanced output - here it means show terminal input/output in tool - def check_advanced_switch(e): - """Check the box to enable advanced output.""" - if self.advanced_switch.value: - logger.info("Enable advanced output.") - self.state.advanced = True - else: - logger.info("Disable advanced output.") - self.state.advanced = False - - self.advanced_switch = Switch( - label="Advanced output", - on_change=check_advanced_switch, - disabled=False, - ) - # inform the user about the device detection self.device_name = Text("", weight="bold") self.device_detection_infobox = Row( @@ -168,7 +152,7 @@ def check_advanced_switch(e): Column( [ self.device_detection_infobox, - Row([self.bootloader_switch, self.advanced_switch]), + Row([self.bootloader_switch]), ] ), Row( diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 534b2d2e..39b21613 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -26,6 +26,7 @@ icons, TextField, Container, + Switch, alignment, colors, ProgressBar, @@ -68,6 +69,29 @@ def __init__( def build(self): """Create the content of a view from step.""" + # error text + self.error_text = Text("", color=colors.RED) + + # switch to enable advanced output - here it means show terminal input/output in tool + def check_advanced_switch(e): + """Check the box to enable advanced output.""" + if self.advanced_switch.value: + logger.info("Enable advanced output.") + self.state.advanced = True + self.terminal_box.visible = True + # add terminal box if enabled + self.right_view.update() + else: + logger.info("Disable advanced output.") + self.state.advanced = False + self.terminal_box.visible = False + self.right_view.update() + + self.advanced_switch = Switch( + label="Advanced output", + on_change=check_advanced_switch, + disabled=False, + ) # text box for terminal output self.terminal_box = Container( content=Column(scroll="auto", expand=True), @@ -78,6 +102,7 @@ def build(self): height=300, border_radius=2, expand=True, + visible=False ) # main controls self.right_view.controls = [ @@ -95,19 +120,33 @@ def build(self): self.call_button = call_button( self.call_to_phone, command=self.step.command ) - self.right_view.controls.append( - Row([self.call_button, self.confirm_button]), - ) - # add terminal box if enabled - if self.state.advanced: - self.right_view.controls.append(Row([self.terminal_box])) + self.right_view.controls.extend([ + Row([self.error_text]), + Column( + [ + self.advanced_switch, + Row([self.call_button, self.confirm_button]), + ] + ), + Row([self.terminal_box]) + ]) elif self.step.type == "call_button_with_input": self.confirm_button.disabled = True self.call_button = call_button( self.call_to_phone, command=self.step.command ) self.right_view.controls.extend( - [self.inputtext, Row([self.call_button, self.confirm_button])] + [ + self.inputtext, + Row([self.error_text]), + Column( + [ + self.advanced_switch, + Row([self.call_button, self.confirm_button]), + ] + ), + Row([self.terminal_box]) + ] ) elif self.step.type == "link_button_with_confirm": self.right_view.controls.extend( @@ -147,16 +186,11 @@ def call_to_phone(self, e, command: str): if self.state.advanced: self.terminal_box.content.controls = [] # display a progress bar to show something is happening - self.right_view.controls.append( - Row( - [ - ProgressBar( - width=600, color="#00d886", bgcolor="#eeeeee", bar_height=16 - ) - ], - alignment="center", - ), + progress_bar = Row( + [ProgressBar(width=600, color="#00d886", bgcolor="#eeeeee", bar_height=16)], + alignment="center", ) + self.right_view.controls.append(progress_bar) self.right_view.update() cmd_mapping = { @@ -171,7 +205,7 @@ def call_to_phone(self, e, command: str): # run the right command if command in cmd_mapping.keys(): for line in cmd_mapping.get(command)(bin_path=self.state.bin_path): - if self.state.advanced and (type(line) == str) and line.strip(): + if (type(line) == str) and line.strip(): self.terminal_box.content.controls.append( Text(f">{line.strip()}", selectable=True) ) @@ -181,7 +215,7 @@ def call_to_phone(self, e, command: str): for line in adb_sideload( bin_path=self.state.bin_path, target=self.state.image_path ): - if self.state.advanced and (type(line) == str) and line.strip(): + if (type(line) == str) and line.strip(): self.terminal_box.content.controls.append( Text(f">{line.strip()}", selectable=True) ) @@ -195,7 +229,7 @@ def call_to_phone(self, e, command: str): Path(f"{self.state.config.metadata.get('devicecode')}.yaml") ), ): - if self.state.advanced and (type(line) == str) and line.strip(): + if (type(line) == str) and line.strip(): self.terminal_box.content.controls.append( Text(f">{line.strip()}", selectable=True) ) @@ -205,7 +239,7 @@ def call_to_phone(self, e, command: str): for line in fastboot_flash_recovery( bin_path=self.state.bin_path, recovery=self.state.recovery_path ): - if self.state.advanced and (type(line) == str) and line.strip(): + if (type(line) == str) and line.strip(): self.terminal_box.content.controls.append( Text(f">{line.strip()}", selectable=True) ) @@ -215,7 +249,7 @@ def call_to_phone(self, e, command: str): for line in fastboot_unlock_with_code( bin_path=self.state.bin_path, unlock_code=self.inputtext.value ): - if self.state.advanced and (type(line) == str) and line.strip(): + if (type(line) == str) and line.strip(): self.terminal_box.content.controls.append( Text(f">{line.strip()}", selectable=True) ) @@ -225,35 +259,31 @@ def call_to_phone(self, e, command: str): for line in heimdall_flash_recovery( bin_path=self.state.bin_path, recovery=self.state.recovery_path ): - if self.state.advanced and (type(line) == str) and line.strip(): + if (type(line) == str) and line.strip(): self.terminal_box.content.controls.append( Text(f">{line.strip()}", selectable=True) ) self.terminal_box.update() success = line else: - logger.error(f"Unknown command type: {command}. Stopping.") - raise Exception(f"Unknown command type: {command}. Stopping.") + msg = f"Unknown command type: {command}. Stopping." + logger.error(msg) + self.error_text.value = msg + raise Exception(msg) # update the view accordingly if not success: # enable call button to retry self.call_button.disabled = False # pop the progress bar - self.right_view.controls.pop() + self.right_view.controls.remove(progress_bar) # also remove the last error text if it happened - if isinstance(self.right_view.controls[-1], Text): - self.right_view.controls.pop() - self.right_view.controls.append( - Text( - f"Command {command} failed! Try again or make sure everything is setup correctly." - ) - ) + self.error_text.value = f"Command {command} failed! Try again or make sure everything is setup correctly." else: sleep(5) # wait to make sure everything is fine logger.success(f"Command {command} run successfully. Allow to continue.") # pop the progress bar - self.right_view.controls.pop() + self.right_view.controls.remove(progress_bar) # emable the confirm buton and disable the call button self.confirm_button.disabled = False self.call_button.disabled = True From 36c326d5f088fd2e9e7750c91a8fdaccf45afd23 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 10:26:50 +0100 Subject: [PATCH 22/35] Refactored terminal box --- openandroidinstaller/tooling.py | 10 +-- openandroidinstaller/views/step_view.py | 112 ++++++++++++------------ 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index 63eee651..3ce5f4f9 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -74,14 +74,6 @@ def adb_reboot_bootloader(bin_path: Path) -> bool: return sleep(1) yield True - # TODO: check if in fastboot mode - # for line in run_command("fastboot", ["devices"], bin_path): - # yield line - # if (type(line) == bool) and not line: - # logger.error("No fastboot mode detected. Reboot into bootloader failed.") - # yield False - # else: - # yield True def adb_reboot_download(bin_path: Path) -> bool: @@ -161,7 +153,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> # TODO: if this fails, a fix can be to just sideload something and then adb reboot for line in run_command( "adb", - ["sideload", str(config_path.parent.parent) + "/helper.txt"], + ["sideload", str(config_path.parent) + "/helper.txt"], bin_path, ): yield line diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 39b21613..1b34fe7c 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -19,6 +19,7 @@ from pathlib import Path from flet import ( + UserControl, Column, ElevatedButton, Row, @@ -78,14 +79,11 @@ def check_advanced_switch(e): if self.advanced_switch.value: logger.info("Enable advanced output.") self.state.advanced = True - self.terminal_box.visible = True - # add terminal box if enabled - self.right_view.update() + self.terminal_box.toggle_visibility() else: logger.info("Disable advanced output.") self.state.advanced = False - self.terminal_box.visible = False - self.right_view.update() + self.terminal_box.toggle_visibility() self.advanced_switch = Switch( label="Advanced output", @@ -93,17 +91,8 @@ def check_advanced_switch(e): disabled=False, ) # text box for terminal output - self.terminal_box = Container( - content=Column(scroll="auto", expand=True), - margin=10, - padding=10, - alignment=alignment.top_left, - bgcolor=colors.BLACK38, - height=300, - border_radius=2, - expand=True, - visible=False - ) + self.terminal_box = TerminalBox(expand=True) + # main controls self.right_view.controls = [ get_title(f"{self.step.title}"), @@ -184,7 +173,7 @@ def call_to_phone(self, e, command: str): self.call_button.disabled = True # reset terminal output if self.state.advanced: - self.terminal_box.content.controls = [] + self.terminal_box.clear() # display a progress bar to show something is happening progress_bar = Row( [ProgressBar(width=600, color="#00d886", bgcolor="#eeeeee", bar_height=16)], @@ -205,71 +194,40 @@ def call_to_phone(self, e, command: str): # run the right command if command in cmd_mapping.keys(): for line in cmd_mapping.get(command)(bin_path=self.state.bin_path): - if (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line + self.terminal_box.write_line(line) elif command == "adb_sideload": for line in adb_sideload( bin_path=self.state.bin_path, target=self.state.image_path ): - if (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line + self.terminal_box.write_line(line) elif command == "adb_twrp_wipe_and_install": for line in adb_twrp_wipe_and_install( bin_path=self.state.bin_path, target=self.state.image_path, - config_path=self.state.config_path.joinpath( - Path(f"{self.state.config.metadata.get('devicecode')}.yaml") - ), + config_path=self.state.config_path, ): - if (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line + self.terminal_box.write_line(line) elif command == "fastboot_flash_recovery": for line in fastboot_flash_recovery( bin_path=self.state.bin_path, recovery=self.state.recovery_path ): - if (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line + self.terminal_box.write_line(line) elif command == "fastboot_unlock_with_code": for line in fastboot_unlock_with_code( bin_path=self.state.bin_path, unlock_code=self.inputtext.value ): - if (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line + self.terminal_box.write_line(line) elif command == "heimdall_flash_recovery": for line in heimdall_flash_recovery( bin_path=self.state.bin_path, recovery=self.state.recovery_path ): - if (type(line) == str) and line.strip(): - self.terminal_box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.terminal_box.update() - success = line + self.terminal_box.write_line(line) else: msg = f"Unknown command type: {command}. Stopping." logger.error(msg) self.error_text.value = msg raise Exception(msg) + success = line # the last element of the iterable is a boolean encoding success/failure # update the view accordingly if not success: @@ -288,3 +246,45 @@ def call_to_phone(self, e, command: str): self.confirm_button.disabled = False self.call_button.disabled = True self.view.update() + + +class TerminalBox(UserControl): + + def __init__(self, expand: bool = True): + super().__init__(expand=expand) + + def build(self): + self.box = Container( + content=Column(scroll="auto", expand=True), + margin=10, + padding=10, + alignment=alignment.top_left, + bgcolor=colors.BLACK38, + height=300, + border_radius=2, + expand=True, + visible=False + ) + return self.box + + def write_line(self, line: str): + """ + Write the line to the window box and update. + + Ignores empty lines. + """ + if (type(line) == str) and line.strip(): + self.box.content.controls.append( + Text(f">{line.strip()}", selectable=True) + ) + self.box.update() + + def toggle_visibility(self): + """Toogle the visibility of the terminal box.""" + self.box.visible = not self.box.visible + self.box.update() + + def clear(self): + """Clear terminal output.""" + self.box.content.controls = [] + self.box.update() \ No newline at end of file From 51893646ec5afab4b75adee378367195bba442e5 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 10:37:35 +0100 Subject: [PATCH 23/35] refactor call_to_phone method --- openandroidinstaller/views/step_view.py | 34 +++++-------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 1b34fe7c..f959346c 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -17,6 +17,7 @@ from time import sleep from typing import Callable from pathlib import Path +from functools import partial from flet import ( UserControl, @@ -182,46 +183,25 @@ def call_to_phone(self, e, command: str): self.right_view.controls.append(progress_bar) self.right_view.update() + # get the appropriate function to run for every possible command. cmd_mapping = { "adb_reboot": adb_reboot, "adb_reboot_bootloader": adb_reboot_bootloader, "adb_reboot_download": adb_reboot_download, + "adb_sideload": partial(adb_sideload, target=self.state.image_path), + "adb_twrp_wipe_and_install": partial(adb_twrp_wipe_and_install, target=self.state.image_path, config_path=self.state.config_path), "fastboot_unlock": fastboot_unlock, + "fastboot_unlock_with_code": partial(fastboot_unlock_with_code, unlock_code=self.inputtext.value), "fastboot_oem_unlock": fastboot_oem_unlock, + "fastboot_flash_recovery": partial(fastboot_flash_recovery, recovery=self.state.recovery_path), "fastboot_reboot": fastboot_reboot, + "heimdall_flash_recovery": partial(heimdall_flash_recovery, recovery=self.state.recovery_path), } # run the right command if command in cmd_mapping.keys(): for line in cmd_mapping.get(command)(bin_path=self.state.bin_path): self.terminal_box.write_line(line) - elif command == "adb_sideload": - for line in adb_sideload( - bin_path=self.state.bin_path, target=self.state.image_path - ): - self.terminal_box.write_line(line) - elif command == "adb_twrp_wipe_and_install": - for line in adb_twrp_wipe_and_install( - bin_path=self.state.bin_path, - target=self.state.image_path, - config_path=self.state.config_path, - ): - self.terminal_box.write_line(line) - elif command == "fastboot_flash_recovery": - for line in fastboot_flash_recovery( - bin_path=self.state.bin_path, recovery=self.state.recovery_path - ): - self.terminal_box.write_line(line) - elif command == "fastboot_unlock_with_code": - for line in fastboot_unlock_with_code( - bin_path=self.state.bin_path, unlock_code=self.inputtext.value - ): - self.terminal_box.write_line(line) - elif command == "heimdall_flash_recovery": - for line in heimdall_flash_recovery( - bin_path=self.state.bin_path, recovery=self.state.recovery_path - ): - self.terminal_box.write_line(line) else: msg = f"Unknown command type: {command}. Stopping." logger.error(msg) From 93322b697a44af8d1e096528602e75fced64f1d9 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 11:17:44 +0100 Subject: [PATCH 24/35] Test for TerminalBox and absolute imports --- openandroidinstaller/app_state.py | 2 +- openandroidinstaller/views/__init__.py | 12 +-- openandroidinstaller/views/base.py | 2 +- .../views/requirements_view.py | 6 +- openandroidinstaller/views/select_view.py | 11 ++- openandroidinstaller/views/start_view.py | 8 +- openandroidinstaller/views/step_view.py | 87 +++++++++++------- openandroidinstaller/views/success_view.py | 6 +- tests/test_terminal_box.py | 91 +++++++++++++++++++ 9 files changed, 168 insertions(+), 57 deletions(-) create mode 100644 tests/test_terminal_box.py diff --git a/openandroidinstaller/app_state.py b/openandroidinstaller/app_state.py index 8a67e66a..23e21a04 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -16,7 +16,7 @@ from pathlib import Path from flet import ProgressBar -from installer_config import _load_config +from openandroidinstaller.installer_config import _load_config class AppState: diff --git a/openandroidinstaller/views/__init__.py b/openandroidinstaller/views/__init__.py index 195cbe55..8e0cc5dd 100644 --- a/openandroidinstaller/views/__init__.py +++ b/openandroidinstaller/views/__init__.py @@ -1,6 +1,6 @@ -from .base import BaseView # noqa -from .start_view import StartView # noqa -from .requirements_view import RequirementsView # noqa -from .select_view import SelectFilesView # noqa -from .step_view import StepView # noqa -from .success_view import SuccessView # noqa +from openandroidinstaller.views.base import BaseView # noqa +from openandroidinstaller.views.start_view import StartView # noqa +from openandroidinstaller.views.requirements_view import RequirementsView # noqa +from openandroidinstaller.views.select_view import SelectFilesView # noqa +from openandroidinstaller.views.step_view import StepView # noqa +from openandroidinstaller.views.success_view import SuccessView # noqa diff --git a/openandroidinstaller/views/base.py b/openandroidinstaller/views/base.py index aa20f7ab..4de7090c 100644 --- a/openandroidinstaller/views/base.py +++ b/openandroidinstaller/views/base.py @@ -13,7 +13,7 @@ # If not, see .""" # Author: Tobias Sterbak -from app_state import AppState +from openandroidinstaller.app_state import AppState from flet import ( Column, diff --git a/openandroidinstaller/views/requirements_view.py b/openandroidinstaller/views/requirements_view.py index 228694b0..45f93368 100644 --- a/openandroidinstaller/views/requirements_view.py +++ b/openandroidinstaller/views/requirements_view.py @@ -28,9 +28,9 @@ icons, ) -from views import BaseView -from app_state import AppState -from widgets import get_title +from openandroidinstaller.views import BaseView +from openandroidinstaller.app_state import AppState +from openandroidinstaller.widgets import get_title class RequirementsView(BaseView): diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 627ff864..61003894 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -29,10 +29,13 @@ FilePickerResultEvent, ) -from views import BaseView -from app_state import AppState -from widgets import get_title, confirm_button -from utils import get_download_link, image_recovery_works_with_device +from openandroidinstaller.views import BaseView +from openandroidinstaller.app_state import AppState +from openandroidinstaller.widgets import get_title, confirm_button +from openandroidinstaller.utils import ( + get_download_link, + image_recovery_works_with_device, +) class SelectFilesView(BaseView): diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py index a3688344..261c532f 100644 --- a/openandroidinstaller/views/start_view.py +++ b/openandroidinstaller/views/start_view.py @@ -31,10 +31,10 @@ icons, ) -from views import BaseView -from app_state import AppState -from widgets import get_title -from tooling import search_device +from openandroidinstaller.views import BaseView +from openandroidinstaller.app_state import AppState +from openandroidinstaller.widgets import get_title +from openandroidinstaller.tooling import search_device class StartView(BaseView): diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index f959346c..68ea16e4 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -16,7 +16,6 @@ from loguru import logger from time import sleep from typing import Callable -from pathlib import Path from functools import partial from flet import ( @@ -34,10 +33,10 @@ ProgressBar, ) -from views import BaseView -from installer_config import Step -from app_state import AppState -from tooling import ( +from openandroidinstaller.views import BaseView +from openandroidinstaller.installer_config import Step +from openandroidinstaller.app_state import AppState +from openandroidinstaller.tooling import ( adb_reboot, adb_reboot_bootloader, adb_reboot_download, @@ -50,7 +49,12 @@ fastboot_unlock_with_code, heimdall_flash_recovery, ) -from widgets import call_button, confirm_button, get_title, link_button +from openandroidinstaller.widgets import ( + call_button, + confirm_button, + get_title, + link_button, +) class StepView(BaseView): @@ -110,16 +114,18 @@ def check_advanced_switch(e): self.call_button = call_button( self.call_to_phone, command=self.step.command ) - self.right_view.controls.extend([ - Row([self.error_text]), - Column( - [ - self.advanced_switch, - Row([self.call_button, self.confirm_button]), - ] - ), - Row([self.terminal_box]) - ]) + self.right_view.controls.extend( + [ + Row([self.error_text]), + Column( + [ + self.advanced_switch, + Row([self.call_button, self.confirm_button]), + ] + ), + Row([self.terminal_box]), + ] + ) elif self.step.type == "call_button_with_input": self.confirm_button.disabled = True self.call_button = call_button( @@ -135,7 +141,7 @@ def check_advanced_switch(e): Row([self.call_button, self.confirm_button]), ] ), - Row([self.terminal_box]) + Row([self.terminal_box]), ] ) elif self.step.type == "link_button_with_confirm": @@ -189,13 +195,23 @@ def call_to_phone(self, e, command: str): "adb_reboot_bootloader": adb_reboot_bootloader, "adb_reboot_download": adb_reboot_download, "adb_sideload": partial(adb_sideload, target=self.state.image_path), - "adb_twrp_wipe_and_install": partial(adb_twrp_wipe_and_install, target=self.state.image_path, config_path=self.state.config_path), + "adb_twrp_wipe_and_install": partial( + adb_twrp_wipe_and_install, + target=self.state.image_path, + config_path=self.state.config_path, + ), "fastboot_unlock": fastboot_unlock, - "fastboot_unlock_with_code": partial(fastboot_unlock_with_code, unlock_code=self.inputtext.value), + "fastboot_unlock_with_code": partial( + fastboot_unlock_with_code, unlock_code=self.inputtext.value + ), "fastboot_oem_unlock": fastboot_oem_unlock, - "fastboot_flash_recovery": partial(fastboot_flash_recovery, recovery=self.state.recovery_path), + "fastboot_flash_recovery": partial( + fastboot_flash_recovery, recovery=self.state.recovery_path + ), "fastboot_reboot": fastboot_reboot, - "heimdall_flash_recovery": partial(heimdall_flash_recovery, recovery=self.state.recovery_path), + "heimdall_flash_recovery": partial( + heimdall_flash_recovery, recovery=self.state.recovery_path + ), } # run the right command @@ -229,12 +245,11 @@ def call_to_phone(self, e, command: str): class TerminalBox(UserControl): - def __init__(self, expand: bool = True): super().__init__(expand=expand) def build(self): - self.box = Container( + self._box = Container( content=Column(scroll="auto", expand=True), margin=10, padding=10, @@ -243,28 +258,30 @@ def build(self): height=300, border_radius=2, expand=True, - visible=False + visible=False, ) - return self.box + return self._box def write_line(self, line: str): """ Write the line to the window box and update. - + Ignores empty lines. """ if (type(line) == str) and line.strip(): - self.box.content.controls.append( - Text(f">{line.strip()}", selectable=True) - ) - self.box.update() - + self._box.content.controls.append(Text(f">{line.strip()}", selectable=True)) + self.update() + def toggle_visibility(self): """Toogle the visibility of the terminal box.""" - self.box.visible = not self.box.visible - self.box.update() + self._box.visible = not self._box.visible + self.update() def clear(self): """Clear terminal output.""" - self.box.content.controls = [] - self.box.update() \ No newline at end of file + self._box.content.controls = [] + self.update() + + def update(self): + """Update the view.""" + self._box.update() diff --git a/openandroidinstaller/views/success_view.py b/openandroidinstaller/views/success_view.py index e62b55a9..1db7eb53 100644 --- a/openandroidinstaller/views/success_view.py +++ b/openandroidinstaller/views/success_view.py @@ -20,9 +20,9 @@ Text, ) -from views import BaseView -from app_state import AppState -from widgets import get_title +from openandroidinstaller.views import BaseView +from openandroidinstaller.app_state import AppState +from openandroidinstaller.widgets import get_title class SuccessView(BaseView): diff --git a/tests/test_terminal_box.py b/tests/test_terminal_box.py new file mode 100644 index 00000000..f4a969db --- /dev/null +++ b/tests/test_terminal_box.py @@ -0,0 +1,91 @@ +"""Test the TerminalBox class.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +import pytest +from flet import Container, Page + +from openandroidinstaller.views.step_view import TerminalBox + + +def test_init_box(): + """Test if the box can be initialized properly.""" + terminal_box = TerminalBox(expand=True) + build_box = terminal_box.build() + + assert isinstance(build_box, Container) + + +def test_write_lines(mocker): + """Test if we can write lines to the terminal and bools are ignored.""" + mocker.patch( + "openandroidinstaller.views.step_view.TerminalBox.update", + return_value=True, + new_callable=mocker.Mock, + ) + + terminal_box = TerminalBox(expand=True) + _ = terminal_box.build() + + # write some lines + for line in ["test", "test_line2", True]: + terminal_box.write_line(line) + + # two text elements should appear + assert len(terminal_box._box.content.controls) == 2 + + +def test_toggle_visibility(mocker): + """Test if the visibility toggle method works.""" + mocker.patch( + "openandroidinstaller.views.step_view.TerminalBox.update", + return_value=True, + new_callable=mocker.Mock, + ) + + terminal_box = TerminalBox(expand=True) + _ = terminal_box.build() + + # should be non-visible at the beginning + assert terminal_box._box.visible == False + # now toggle + terminal_box.toggle_visibility() + # now should be visible + assert terminal_box._box.visible == True + # now toggle again + terminal_box.toggle_visibility() + # now it should be non-visible again + assert terminal_box._box.visible == False + + +def test_clear_terminal(mocker): + """Test if the terminal can be cleared properly.""" + mocker.patch( + "openandroidinstaller.views.step_view.TerminalBox.update", + return_value=True, + new_callable=mocker.Mock, + ) + + terminal_box = TerminalBox(expand=True) + _ = terminal_box.build() + + # write some lines + for line in ["test", "test_line2", True]: + terminal_box.write_line(line) + + # now clear + terminal_box.clear() + + # two text elements should appear + assert len(terminal_box._box.content.controls) == 0 From 76b8964a9bb208a0dd3b11311f275862c28474cb Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 11:38:53 +0100 Subject: [PATCH 25/35] Fix makefile for tests and remove absolute imports again --- Makefile | 2 +- openandroidinstaller/__init__.py | 0 openandroidinstaller/app_state.py | 2 +- openandroidinstaller/openandroidinstaller.py | 3 ++- openandroidinstaller/views/__init__.py | 12 ++++++------ openandroidinstaller/views/base.py | 2 +- openandroidinstaller/views/requirements_view.py | 6 +++--- openandroidinstaller/views/select_view.py | 8 ++++---- openandroidinstaller/views/start_view.py | 8 ++++---- openandroidinstaller/views/step_view.py | 10 +++++----- openandroidinstaller/views/success_view.py | 6 +++--- tests/__init__.py | 0 12 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 openandroidinstaller/__init__.py create mode 100644 tests/__init__.py diff --git a/Makefile b/Makefile index 57bbe86e..a3e2d42f 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ lint: test: poetry run black . poetry run ruff openandroidinstaller/ --ignore E501 - poetry run pytest --cov=openandroidinstaller tests/ + PYTHONPATH=openandroidinstaller:$(PYTHONPATH) poetry run pytest --cov=openandroidinstaller tests/ app: poetry run python openandroidinstaller/openandroidinstaller.py diff --git a/openandroidinstaller/__init__.py b/openandroidinstaller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/openandroidinstaller/app_state.py b/openandroidinstaller/app_state.py index 23e21a04..8a67e66a 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -16,7 +16,7 @@ from pathlib import Path from flet import ProgressBar -from openandroidinstaller.installer_config import _load_config +from installer_config import _load_config class AppState: diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 4dfefa8e..0c73279f 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -19,7 +19,6 @@ from pathlib import Path import flet as ft -from app_state import AppState from flet import ( AppBar, Banner, @@ -38,6 +37,8 @@ icons, ) from loguru import logger + +from app_state import AppState from views import SelectFilesView, StepView, SuccessView, StartView, RequirementsView from tooling import run_command diff --git a/openandroidinstaller/views/__init__.py b/openandroidinstaller/views/__init__.py index 8e0cc5dd..195cbe55 100644 --- a/openandroidinstaller/views/__init__.py +++ b/openandroidinstaller/views/__init__.py @@ -1,6 +1,6 @@ -from openandroidinstaller.views.base import BaseView # noqa -from openandroidinstaller.views.start_view import StartView # noqa -from openandroidinstaller.views.requirements_view import RequirementsView # noqa -from openandroidinstaller.views.select_view import SelectFilesView # noqa -from openandroidinstaller.views.step_view import StepView # noqa -from openandroidinstaller.views.success_view import SuccessView # noqa +from .base import BaseView # noqa +from .start_view import StartView # noqa +from .requirements_view import RequirementsView # noqa +from .select_view import SelectFilesView # noqa +from .step_view import StepView # noqa +from .success_view import SuccessView # noqa diff --git a/openandroidinstaller/views/base.py b/openandroidinstaller/views/base.py index 4de7090c..aa20f7ab 100644 --- a/openandroidinstaller/views/base.py +++ b/openandroidinstaller/views/base.py @@ -13,7 +13,7 @@ # If not, see .""" # Author: Tobias Sterbak -from openandroidinstaller.app_state import AppState +from app_state import AppState from flet import ( Column, diff --git a/openandroidinstaller/views/requirements_view.py b/openandroidinstaller/views/requirements_view.py index 45f93368..228694b0 100644 --- a/openandroidinstaller/views/requirements_view.py +++ b/openandroidinstaller/views/requirements_view.py @@ -28,9 +28,9 @@ icons, ) -from openandroidinstaller.views import BaseView -from openandroidinstaller.app_state import AppState -from openandroidinstaller.widgets import get_title +from views import BaseView +from app_state import AppState +from widgets import get_title class RequirementsView(BaseView): diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 61003894..7ee9a3c5 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -29,10 +29,10 @@ FilePickerResultEvent, ) -from openandroidinstaller.views import BaseView -from openandroidinstaller.app_state import AppState -from openandroidinstaller.widgets import get_title, confirm_button -from openandroidinstaller.utils import ( +from views import BaseView +from app_state import AppState +from widgets import get_title, confirm_button +from utils import ( get_download_link, image_recovery_works_with_device, ) diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py index 261c532f..a3688344 100644 --- a/openandroidinstaller/views/start_view.py +++ b/openandroidinstaller/views/start_view.py @@ -31,10 +31,10 @@ icons, ) -from openandroidinstaller.views import BaseView -from openandroidinstaller.app_state import AppState -from openandroidinstaller.widgets import get_title -from openandroidinstaller.tooling import search_device +from views import BaseView +from app_state import AppState +from widgets import get_title +from tooling import search_device class StartView(BaseView): diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 68ea16e4..de543dd0 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -33,10 +33,10 @@ ProgressBar, ) -from openandroidinstaller.views import BaseView -from openandroidinstaller.installer_config import Step -from openandroidinstaller.app_state import AppState -from openandroidinstaller.tooling import ( +from views import BaseView +from installer_config import Step +from app_state import AppState +from tooling import ( adb_reboot, adb_reboot_bootloader, adb_reboot_download, @@ -49,7 +49,7 @@ fastboot_unlock_with_code, heimdall_flash_recovery, ) -from openandroidinstaller.widgets import ( +from widgets import ( call_button, confirm_button, get_title, diff --git a/openandroidinstaller/views/success_view.py b/openandroidinstaller/views/success_view.py index 1db7eb53..e62b55a9 100644 --- a/openandroidinstaller/views/success_view.py +++ b/openandroidinstaller/views/success_view.py @@ -20,9 +20,9 @@ Text, ) -from openandroidinstaller.views import BaseView -from openandroidinstaller.app_state import AppState -from openandroidinstaller.widgets import get_title +from views import BaseView +from app_state import AppState +from widgets import get_title class SuccessView(BaseView): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b From 37ae9cc32ba4d946d767d0c63595ce033e773580 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 12:52:27 +0100 Subject: [PATCH 26/35] Enable copy_partitions sideloading and basic config for moto g7 power --- README.md | 1 + .../assets/configs/ocean.yaml | 55 ++++++++++++++++++ .../copy-partitions-20220613-signed.zip | Bin 0 -> 4042 bytes openandroidinstaller/installer_config.py | 2 + openandroidinstaller/tooling.py | 32 +++++++++- openandroidinstaller/views/step_view.py | 1 + 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 openandroidinstaller/assets/configs/ocean.yaml create mode 100644 openandroidinstaller/assets/copy-partitions-20220613-signed.zip diff --git a/README.md b/README.md index 9299ec1e..be392b29 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ Other phone vendors stops allowing to unlock the bootloader all together. There - The [Android SDK Platform Tools](https://developer.android.com/studio/releases/platform-tools) (such as adb and fastboot) are [Apache](https://android.googlesource.com/platform/system/adb/+/refs/heads/master/NOTICE)-licensed universal Android utilities - [Heimdall](https://gitlab.com/BenjaminDobell/Heimdall/) is an [MIT](https://gitlab.com/BenjaminDobell/Heimdall/-/blob/master/LICENSE)-licensed replacement for the leaked ODIN tool to flash Samsung devices. - [libusb-1.0](https://github.com/libusb/libusb) is a [LGPL-2.1](https://github.com/libusb/libusb/blob/master/COPYING)-licensed library for USB device access from Linux, macOS, Windows and others. +- [copy-partitions-20220613-signed.zip](https://mirrorbits.lineageos.org/tools/copy-partitions-20220613-signed.zip) The copy-partitions script was created by LineageOS developer erfanoabdi and filipepferraz and released under LGPL. It is used when the partitions need to be copied before flashing. ## Acknowledgements diff --git a/openandroidinstaller/assets/configs/ocean.yaml b/openandroidinstaller/assets/configs/ocean.yaml new file mode 100644 index 00000000..f14db97b --- /dev/null +++ b/openandroidinstaller/assets/configs/ocean.yaml @@ -0,0 +1,55 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: Moto G7 power + devicecode: Ocean +requirements: + copy_partitions: True +steps: + unlock_bootloader: + - type: confirm_button + content: > + As a first step, you need to unlock the bootloader of your device. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). On the device, dial *#06# to launch the service menu. + - type: confirm_button + content: Go to service info > configuration and check rooting status - you can only continue if it says 'Bootloader unlock allowed":" Yes.' + - type: confirm_button + content: > + Now go to Settings 'About the phone' and then Status. Write down the IMEI of your device. You will need it in the next step. + - type: link_button_with_confirm + content: > + Click on the button to open the instructions on Motorola's official unlocking website to generate an unlock code for your bootloader. + Once you got the code write it down somewhere and continue to input the code. + link: https://motorola-global-portal.custhelp.com/app/standalone/bootloader/unlock-your-device-a + - type: confirm_button + content: Connect the device to your PC via USB. And confirm to continue. + - type: call_button + content: Press the button to reboot into the bootloader now. When the notification light turns blue, confirm to continue. + command: adb_reboot_bootloader + - type: call_button_with_input + content: > + Use your code to unlock the bootloader of your device. Type in the full 18 character code starting with 0x (Example: 0x3EC4F7AD6E0B32B6). + If you already did that, you can skip this step. + command: fastboot_unlock_with_code + - type: call_button + content: > + Press the button to reboot. Since the device resets completely, you will need to re-enable USB debugging to continue. + Connect your device to your PC via USB. Then confirm here to continue. + command: fastboot_reboot + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + install_os: + - type: call_button + content: > + In the next steps, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/copy-partitions-20220613-signed.zip b/openandroidinstaller/assets/copy-partitions-20220613-signed.zip new file mode 100644 index 0000000000000000000000000000000000000000..b2bcc35322c8e36251056e597052da0cce313e34 GIT binary patch literal 4042 zcmc(hcT`hp7Qje1fi$Fnd@vCuS)~_Mw*3IEWA^n!#&&NYt#KWrgV7mcdaK=I~oBhHykp z=4{0hkxV>?kbqVH_^jk{kKPm~7(q)Ww-gOhshYV#O*$tycfx>W+C% z3)7) ztw6D7iB3(}k*guiOy2={qcd4`-6^!Sh^_QXY{5YWVP^*O4wwZST=bLVZxW2gIk03h z7`T?%d7LVcW7+U8H#x}WvJaIH&?u3u8I>(;CC`=#tfLn0aA{&iPZZdSsdWRz4W=^v zr6^Ng@5n>v8hW3{vq-=`dEwDxm#US7;(f={lS3Ww_|1EXtnbC+0bx5ev)hh54^kTQ z*?QS`K|uvCu5XO=l)Po@PcimCKQR__P*kS%%e8a9`(Nivyc`cEzd>vRO&q^0dl)n{ZVeU>}LCl^| zLj4LB3o)WE4(R%fNw6=gmBxMScO+5nFeuZ@-`riK_($m z{6>~4-VFW@aOW_tGvl}FuH6LM{6e7_Dyn#?zWa2={@2t^$%=REwOy&+EDrXkFKygi zrk}*a{DFv>EM9(|@0Gt<3qqAk8V$8iZsPLwW96>I%3b45V3=IA7_Xqzr=s0IsR&II zu!Tz`b@nOsaYWrkFI@i@#(EB2E(hOgb|1Mu9}jhr=QS!a2)~-ax`mdog(5}|*XAft z_HbuxCnbDbq*E&-6yI~YE1Trh+qQ5xYOb zWu(GbC#pYlXrfo4m*#~UXVdR+{dzWxq>D-0#PG@Dos)GvB4SFMbVUaVUW!b_7AQ3M zsd=O`G#i%{*LuLs*Z*U_;@O#WhY=bri`NPqu^o(8aOgV^NVQ|ZkSH(>4K!bl{n~x@ zZ+T1z*jPM}m?h$0ZzT4%4XW{rXrKgyE;|yk)jZH(vYsPl{oqssd-=_AG1DYt|C_ob z^@!Zk0_QB<${p3I*DhC5dT$bQ+pb8prigTjkFkvF;IK(ah-9vbjy(8~vkEiAcb&@Y zwgw!;$Tor9f95b9PdO)*^YGAgdE{rsn8J?j>Xn>~mDSs@;rUjR*m{`YBw%|!>xPM% zx+|Zhavr%wX7ck+xxAeW&v(M&`lZ>Dh-dfm#|m6NG<}ZyxP=#jZ5fBHd2Y6Ds&lTS z@rFESmtGe4G>3(b+)@3?eA?0YHOju*=w*vvFAAYSj}iay9O|{QZJ83}{ghcxEBHYz z=2?%9l~v|RysFfmH@9MRf&4bMqQw0jWX$~idF!8L<&8hBq#Zh&o7sjL<}jJ+v#i`Z zHT`_%-Z`R?9g%%+ol#2U?o`1t^9Pk2Pu9~jhhwqGJgT#z9K@Xv)1dS$G*M zFW!e%Etwb*bR3+TMJHaR83^lytGr5*S59ejvqjYTiC_Qp=I(|Pd!0rmZ}VL@mPZqP zccm@m+C8B{0F!To`p1~v@$8wA4dLpKXUnZNgGh3phI2oX@-MSBG|0?~(%dD>Jz)}~ zqlx|zKkwMErSWSqq!AF@a^9V?GpGH^(t)?Jk+^y~y7O>h@}r4OpI9BpIIBo`avD@5dOMZ;RP< z#ubjCpZms2A(CxUMbT}gA6FWA;w;sq=QI`88zYUX+dL%6kRGJA4;(-E?^nV%wNkniRk_di*B!Y5DU=R=lq6N%C*t&z; zJ1vone;5Lx)b9fR=>}{Cp^tCq>qt}Exazur8 zjw^q*Ub|uHBpzPt1BhgA8*EEu>|)cKc1?}XugNKhn3YHf=FzC^60Erugl`F)X02tu zJ>6%0qZAP`Fh>?yn>XEn>oKNx4t=M4e23bKN|%Lt13_PZF|JrEcO02X187D#^TU*~6na6;IFvJ_8^`IwnI zlG44}WFj<291ZM{f=91KA>2sLnE!0RaAd#+fg-^`Amw<@sauhnb08oI5e@9^mJ4*B zZZp2Up@YqX5mF>helyoD&w3Zjg<6?qHM|4u=!iMLD$p%2X-2Slm0 z-|JN^j)os_p$8th`6f+WE#!(GLq_nHZE-t9Ud9@(T*#8s+m0D} zh&EJUog3XsFUoD$DKD9^4p&=IAOas!_Z^8^-^eTf z`%*X#ieqp&#tdq4K!`wqqRGYOGO2^+t#Q-`Ilj<3*%qH#_Y-|CP3ZgZR^|DyaG!8y znWc}=rSC8WciN#{mF$xS#VXq#FTxz2@a_VX;Jn&~@6hY6j|46lpRQLA`Reyx?gVDS zsYA7777j9FeZl?Y)ZiS>J1XABs3URZy?$sN>dXYn(a!s^b=m?yfz`$=k>qEVf<(UG zzm9)0bbtQpy|3J%U|oapbOrD(<%LE0@^fjkt+cJJ|x z#M}YgguPm|;rZ(};Za`L18?E)dhf4R(Z^45{OWv(ASs_U-O^HQZc%(bB$dxN@R_Ip X6SOLRQU84q?7)VVCm%|oR6zd$tjByD literal 0 HcmV?d00001 diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index ffbec9f4..83956384 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -62,6 +62,7 @@ def __init__( self.install_os = install_os self.metadata = metadata self.requirements = requirements + self.copy_partitions = requirements.get("copy_partitions", False) @classmethod def from_file(cls, path): @@ -153,6 +154,7 @@ def validate_config(config: str) -> bool: schema.Optional("requirements"): { schema.Optional("android"): schema.Or(str, int), schema.Optional("firmware"): str, + schema.Optional("copy_partitions"): bool, }, "steps": { "unlock_bootloader": schema.Or(None, [step_schema]), diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index 3ce5f4f9..1b93d13c 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -101,13 +101,43 @@ def adb_sideload(bin_path: Path, target: str) -> bool: yield True -def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> bool: +def adb_twrp_wipe_and_install( + bin_path: Path, target: str, config_path: Path, copy_partitions: bool = False +) -> bool: """Wipe and format data with twrp, then flash os image with adb. Only works for twrp recovery. """ logger.info("Wipe and format data with twrp, then install os image.") sleep(7) + # some devices like one plus 6t or motorola moto g7 power need the partitions copied to prevent a hardbrick + if copy_partitions: + logger.info("Sideload copy_partitions script with adb.") + # activate sideload + for line in run_command("adb", ["shell", "twrp", "sideload"], bin_path): + yield line + if (type(line) == bool) and not line: + logger.error("Activating sideload failed.") + yield False + return + # now sideload the script + sleep(5) + logger.info("Sideload the copy_partitions script") + for line in run_command( + "adb", + [ + "sideload", + str(config_path.parent) + "/copy-partitions-20220613-signed.zip", + ], + bin_path, + ): + yield line + if (type(line) == bool) and not line: + logger.error("Sideloading copy-partitions-20220613-signed.zip failed.") + sleep(5) + # Copy partitions end # + + # now perform a factory reset for line in run_command("adb", ["shell", "twrp", "format", "data"], bin_path): yield line if (type(line) == bool) and not line: diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index de543dd0..7b65fde3 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -199,6 +199,7 @@ def call_to_phone(self, e, command: str): adb_twrp_wipe_and_install, target=self.state.image_path, config_path=self.state.config_path, + copy_partitions=self.state.config.copy_partitions, ), "fastboot_unlock": fastboot_unlock, "fastboot_unlock_with_code": partial( From 872a727c6d3adf66f78c09d5142574536644ce02 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 15:33:54 +0100 Subject: [PATCH 27/35] Config for Motorola moto g7 power --- README.md | 6 +- .../assets/configs/ocean.yaml | 40 +++++---- openandroidinstaller/installer_config.py | 4 +- openandroidinstaller/openandroidinstaller.py | 2 +- openandroidinstaller/tooling.py | 81 ++++++++++++------- openandroidinstaller/views/step_view.py | 5 +- 6 files changed, 85 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index be392b29..0bf31f46 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Samsung | Galaxy S7 | [herolte](https://wiki.lineageos.org/devices/herolte/) | S Samsung | Galaxy S9 | [starlte](https://wiki.lineageos.org/devices/starlte/) | | tested Samsung | Galaxy S10 | beyond1lte | | tested Google | Pixel 3a | [sargo](https://wiki.lineageos.org/devices/sargo/) | sargo | tested -Google | Pixel 4 | flame | flame | tested +Google | Pixel 4 | [flame](https://wiki.lineageos.org/devices/flame/) | flame | tested Google | Pixel 4a | sunfish | sunfish | tested Google | Pixel 5 | redfin | redfin | tested Google | Pixel 5a | barbet | barbet | tested @@ -54,7 +54,7 @@ Sony | Xperia ZX | kagura | | planned Fairphone | Fairphone 2 | [FP2](https://wiki.lineageos.org/devices/FP2/) | | tested Fairphone | Fairphone 3 | [FP3](https://wiki.lineageos.org/devices/FP3/) | | tested Motorola | moto G5 | cedric | | planned -Motorola | moto g7 power | ocean | | under development +Motorola | moto g7 power | [ocean](https://wiki.lineageos.org/devices/ocean/) | | tested OnePlus | 6 | enchilada | | under development OnePlus | 6T | fajita | | under development OnePlus | 7T | hotdogb | | under development @@ -105,7 +105,7 @@ Every step in the config file corresponds to one view in the application. These - `call_button_with_input`: Display the content text, an input field and a button that runs a given command. The inputtext, can be used in the command by using the `` placeholder in the command field. After the command is run, a confirm button is displayed to allow the user to move to the next step. - `link_button_with_confirm`: Display a button that opens a browser with a given link, confirm afterwards. Link is given in `link`. - `content`: str; The content text displayed alongside the action of the step. Used to inform the user about whats going on. -- `command`: [ONLY for call_button* steps] str; The command to run. One of `adb_reboot`, `adb_reboot_bootloader`, `adb_reboot_download`, `adb_sideload`, `adb_twrp_wipe_and_install`, `fastboot_flash_recovery`, `fastboot_unlock_with_code`, `fastboot_unlock`, `fastboot_oem_unlock`, `fastboot_reboot`, `heimdall_flash_recovery`. +- `command`: [ONLY for call_button* steps] str; The command to run. One of `adb_reboot`, `adb_reboot_bootloader`, `adb_reboot_download`, `adb_sideload`, `adb_twrp_wipe_and_install`, `adb_twrp_copy_partitions`, `fastboot_flash_recovery`, `fastboot_unlock_with_code`, `fastboot_unlock`, `fastboot_oem_unlock`, `fastboot_get_unlock_data`, `fastboot_reboot`, `heimdall_flash_recovery`. - `img`: [OPTIONAL] Display an image on the left pane of the step view. Images are loaded from `openandroidinstaller/assets/imgs/`. - `allow_skip`: [OPTIONAL] boolean; If a skip button should be displayed to allow skipping this step. Can be useful when the bootloader is already unlocked. - `link`: [OPTIONAL] Link to use for the link button if type is `link_button_with_confirm`. diff --git a/openandroidinstaller/assets/configs/ocean.yaml b/openandroidinstaller/assets/configs/ocean.yaml index f14db97b..0aaf796c 100644 --- a/openandroidinstaller/assets/configs/ocean.yaml +++ b/openandroidinstaller/assets/configs/ocean.yaml @@ -1,34 +1,33 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Moto G7 power - devicecode: Ocean -requirements: - copy_partitions: True + devicecode: ocean steps: unlock_bootloader: - - type: confirm_button + - type: call_button + command: adb_reboot_bootloader content: > As a first step, you need to unlock the bootloader of your device. A bootloader is the piece of software, that tells your phone - how to start and run an operating system (like Android). On the device, dial *#06# to launch the service menu. - - type: confirm_button - content: Go to service info > configuration and check rooting status - you can only continue if it says 'Bootloader unlock allowed":" Yes.' - - type: confirm_button + how to start and run an operating system (like Android). You need to boot into fastboot mode by pressing the 'Confirm and run' button. Then continue. + - type: call_button + command: fastboot_get_unlock_data content: > - Now go to Settings 'About the phone' and then Status. Write down the IMEI of your device. You will need it in the next step. + Now you need to get your device ID to get an unlock code from Motorola. Press 'Confirm and run' to get the ID. (You need to toggle 'Advanced Output' here to see it.) + Copy it to a separate file to use it in the next step. - type: link_button_with_confirm content: > Click on the button to open the instructions on Motorola's official unlocking website to generate an unlock code for your bootloader. - Once you got the code write it down somewhere and continue to input the code. + Copy the code from the last step to the website and follow the instructions there. Then continue here. link: https://motorola-global-portal.custhelp.com/app/standalone/bootloader/unlock-your-device-a - type: confirm_button content: Connect the device to your PC via USB. And confirm to continue. - - type: call_button - content: Press the button to reboot into the bootloader now. When the notification light turns blue, confirm to continue. - command: adb_reboot_bootloader - type: call_button_with_input content: > - Use your code to unlock the bootloader of your device. Type in the full 18 character code starting with 0x (Example: 0x3EC4F7AD6E0B32B6). - If you already did that, you can skip this step. + Use your code to unlock the bootloader of your device. Type in the full 21 character code you received by email (Example: 5RTSQCYL7ZJKL4NN35MY). Then confirm an run. Afterwards you can continue. + command: fastboot_unlock_with_code + - type: call_button_with_input + content: > + You need to perform the last command again, so reenter the code and run again. Afterwards you can continue. command: fastboot_unlock_with_code - type: call_button content: > @@ -45,10 +44,19 @@ steps: - type: call_button content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. command: fastboot_flash_recovery + - type: call_button + command: adb_twrp_copy_partitions + content: > + In some cases, the inactive slot can be unpopulated or contain much older firmware than the active slot, leading to various issues including a potential hard-brick. + We can ensure none of that will happen by copying the contents of the active slot to the inactive slot. Press 'confirm and run' to to this. Once you are in the bootloader again, continue. + - type: call_button + command: fastboot_flash_recovery + content: > + Now we need to boot into recovery again. Press run and when you see the TWRP screen you can continue. install_os: - type: call_button content: > - In the next steps, you finally flash the selected OS image. + In this last step, you finally flash the selected OS image. Wait until the TWRP screen appears. Then run the command. This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 83956384..04b9c2ca 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -62,7 +62,6 @@ def __init__( self.install_os = install_os self.metadata = metadata self.requirements = requirements - self.copy_partitions = requirements.get("copy_partitions", False) @classmethod def from_file(cls, path): @@ -137,7 +136,7 @@ def validate_config(config: str) -> bool: ), "content": str, schema.Optional("command"): Regex( - r"adb_reboot|adb_reboot_bootloader|adb_reboot_download|adb_sideload|adb_twrp_wipe_and_install|fastboot_flash_recovery|fastboot_unlock_with_code|fastboot_unlock|fastboot_oem_unlock|fastboot_reboot|heimdall_flash_recovery" +r"adb_reboot|adb_reboot_bootloader|adb_reboot_download|adb_sideload|adb_twrp_wipe_and_install|adb_twrp_copy_partitions|fastboot_flash_recovery|fastboot_unlock_with_code|fastboot_get_unlock_data|fastboot_unlock|fastboot_oem_unlock|fastboot_reboot|heimdall_flash_recovery" ), schema.Optional("allow_skip"): bool, schema.Optional("img"): str, @@ -154,7 +153,6 @@ def validate_config(config: str) -> bool: schema.Optional("requirements"): { schema.Optional("android"): schema.Or(str, int), schema.Optional("firmware"): str, - schema.Optional("copy_partitions"): bool, }, "steps": { "unlock_bootloader": schema.Or(None, [step_schema]), diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 0c73279f..7ffaa0bb 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -46,7 +46,7 @@ logger.add("openandroidinstaller.log") # Toggle to True for development purposes -DEVELOPMENT = True +DEVELOPMENT = False DEVELOPMENT_CONFIG = "sargo" # "a3y17lte" # "sargo" diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index 1b93d13c..0998ab59 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -101,8 +101,46 @@ def adb_sideload(bin_path: Path, target: str) -> bool: yield True +def adb_twrp_copy_partitions(bin_path: Path, config_path: Path): + # some devices like one plus 6t or motorola moto g7 power need the partitions copied to prevent a hard brick + logger.info("Sideload copy_partitions script with adb.") + # activate sideload + for line in run_command("adb", ["shell", "twrp", "sideload"], bin_path): + yield line + if (type(line) == bool) and not line: + logger.error("Activating sideload failed.") + yield False + return + # now sideload the script + sleep(5) + logger.info("Sideload the copy_partitions script") + for line in run_command( + "adb", + [ + "sideload", + str(config_path.parent) + "/copy-partitions-20220613-signed.zip", + ], + bin_path, + ): + yield line + if (type(line) == bool) and not line: + logger.error("Sideloading copy-partitions-20220613-signed.zip failed.") + sleep(10) + # reboot into the bootloader again + logger.info("Rebooting device into bootloader with adb.") + for line in run_command("adb", ["reboot", "bootloader"], bin_path): + yield line + if (type(line) == bool) and not line: + logger.error("Reboot into bootloader failed.") + yield False + return + sleep(7) + # Copy partitions end # + return True + + def adb_twrp_wipe_and_install( - bin_path: Path, target: str, config_path: Path, copy_partitions: bool = False + bin_path: Path, target: str, config_path: Path ) -> bool: """Wipe and format data with twrp, then flash os image with adb. @@ -110,33 +148,6 @@ def adb_twrp_wipe_and_install( """ logger.info("Wipe and format data with twrp, then install os image.") sleep(7) - # some devices like one plus 6t or motorola moto g7 power need the partitions copied to prevent a hardbrick - if copy_partitions: - logger.info("Sideload copy_partitions script with adb.") - # activate sideload - for line in run_command("adb", ["shell", "twrp", "sideload"], bin_path): - yield line - if (type(line) == bool) and not line: - logger.error("Activating sideload failed.") - yield False - return - # now sideload the script - sleep(5) - logger.info("Sideload the copy_partitions script") - for line in run_command( - "adb", - [ - "sideload", - str(config_path.parent) + "/copy-partitions-20220613-signed.zip", - ], - bin_path, - ): - yield line - if (type(line) == bool) and not line: - logger.error("Sideloading copy-partitions-20220613-signed.zip failed.") - sleep(5) - # Copy partitions end # - # now perform a factory reset for line in run_command("adb", ["shell", "twrp", "format", "data"], bin_path): yield line @@ -193,7 +204,7 @@ def adb_twrp_wipe_and_install( return break # finally reboot into os - sleep(5) + sleep(7) logger.info("Reboot into OS.") for line in run_command("adb", ["reboot"], bin_path): # "shell", "twrp", yield line @@ -241,6 +252,18 @@ def fastboot_oem_unlock(bin_path: Path) -> bool: yield True +def fastboot_get_unlock_data(bin_path: Path) -> bool: + """Get the unlock data with fastboot""" + logger.info("Get unlock data with fastboot") + for line in run_command("fastboot", ["oem", "get_unlock_data"], bin_path): + yield line + if (type(line) == bool) and not line: + logger.error("Getting unlock data failed.") + yield False + else: + yield True + + def fastboot_reboot(bin_path: Path) -> bool: """Reboot with fastboot""" logger.info("Rebooting device with fastboot.") diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 7b65fde3..fe1caf87 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -42,11 +42,13 @@ adb_reboot_download, adb_sideload, adb_twrp_wipe_and_install, + adb_twrp_copy_partitions, fastboot_flash_recovery, fastboot_oem_unlock, fastboot_reboot, fastboot_unlock, fastboot_unlock_with_code, + fastboot_get_unlock_data, heimdall_flash_recovery, ) from widgets import ( @@ -199,13 +201,14 @@ def call_to_phone(self, e, command: str): adb_twrp_wipe_and_install, target=self.state.image_path, config_path=self.state.config_path, - copy_partitions=self.state.config.copy_partitions, ), + "adb_twrp_copy_partitions": partial(adb_twrp_copy_partitions, config_path=self.state.config_path), "fastboot_unlock": fastboot_unlock, "fastboot_unlock_with_code": partial( fastboot_unlock_with_code, unlock_code=self.inputtext.value ), "fastboot_oem_unlock": fastboot_oem_unlock, + "fastboot_get_unlock_data": fastboot_get_unlock_data, "fastboot_flash_recovery": partial( fastboot_flash_recovery, recovery=self.state.recovery_path ), From 1cdeafa0357494c1ce85c8d08dd2b6c5ccfa85b5 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 15:35:29 +0100 Subject: [PATCH 28/35] Add config for moto g5 (cedric) --- README.md | 2 +- .../assets/configs/cedric.yaml | 54 +++++++++++++++++++ openandroidinstaller/installer_config.py | 2 +- openandroidinstaller/openandroidinstaller.py | 2 +- openandroidinstaller/tooling.py | 4 +- openandroidinstaller/views/step_view.py | 4 +- 6 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 openandroidinstaller/assets/configs/cedric.yaml diff --git a/README.md b/README.md index 0bf31f46..a79c0165 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Sony | Xperia Z3 | [z3](https://wiki.lineageos.org/devices/z3/) | | tested Sony | Xperia ZX | kagura | | planned Fairphone | Fairphone 2 | [FP2](https://wiki.lineageos.org/devices/FP2/) | | tested Fairphone | Fairphone 3 | [FP3](https://wiki.lineageos.org/devices/FP3/) | | tested -Motorola | moto G5 | cedric | | planned +Motorola | moto G5 | [cedric](https://wiki.lineageos.org/devices/cedric/) | | tested Motorola | moto g7 power | [ocean](https://wiki.lineageos.org/devices/ocean/) | | tested OnePlus | 6 | enchilada | | under development OnePlus | 6T | fajita | | under development diff --git a/openandroidinstaller/assets/configs/cedric.yaml b/openandroidinstaller/assets/configs/cedric.yaml new file mode 100644 index 00000000..8d2ed852 --- /dev/null +++ b/openandroidinstaller/assets/configs/cedric.yaml @@ -0,0 +1,54 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: Moto G5 + devicecode: cedric +steps: + unlock_bootloader: + - type: call_button + command: adb_reboot_bootloader + content: > + As a first step, you need to unlock the bootloader of your device. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). You need to boot into fastboot mode by pressing the 'Confirm and run' button. Then continue. + - type: call_button + command: fastboot_get_unlock_data + content: > + Now you need to get your device ID to get an unlock code from Motorola. Press 'Confirm and run' to get the ID. (You need to toggle 'Advanced Output' here to see it.) + Copy it to a separate file to use it in the next step. + - type: link_button_with_confirm + content: > + Click on the button to open the instructions on Motorola's official unlocking website to generate an unlock code for your bootloader. + Copy the code from the last step to the website and follow the instructions there. Then continue here. + link: https://motorola-global-portal.custhelp.com/app/standalone/bootloader/unlock-your-device-a + - type: confirm_button + content: Connect the device to your PC via USB. And confirm to continue. + - type: call_button_with_input + content: > + Use your code to unlock the bootloader of your device. Type in the full 21 character code you received by email (Example: 5RTSQCYL7ZJKL4NN35MY). Then confirm an run. Afterwards you can continue. + command: fastboot_unlock_with_code + - type: call_button_with_input + content: > + You need to perform the last command again, so reenter the code and run again. Afterwards you can continue. + command: fastboot_unlock_with_code + - type: call_button + content: > + Press the button to reboot. Since the device resets completely, you will need to re-enable USB debugging to continue. + Connect your device to your PC via USB. Then confirm here to continue. + command: fastboot_reboot + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + install_os: + - type: call_button + content: > + In this last step, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 04b9c2ca..36f7c2c9 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -136,7 +136,7 @@ def validate_config(config: str) -> bool: ), "content": str, schema.Optional("command"): Regex( -r"adb_reboot|adb_reboot_bootloader|adb_reboot_download|adb_sideload|adb_twrp_wipe_and_install|adb_twrp_copy_partitions|fastboot_flash_recovery|fastboot_unlock_with_code|fastboot_get_unlock_data|fastboot_unlock|fastboot_oem_unlock|fastboot_reboot|heimdall_flash_recovery" + r"adb_reboot|adb_reboot_bootloader|adb_reboot_download|adb_sideload|adb_twrp_wipe_and_install|adb_twrp_copy_partitions|fastboot_flash_recovery|fastboot_unlock_with_code|fastboot_get_unlock_data|fastboot_unlock|fastboot_oem_unlock|fastboot_reboot|heimdall_flash_recovery" ), schema.Optional("allow_skip"): bool, schema.Optional("img"): str, diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index 7ffaa0bb..f6d36f35 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -46,7 +46,7 @@ logger.add("openandroidinstaller.log") # Toggle to True for development purposes -DEVELOPMENT = False +DEVELOPMENT = False DEVELOPMENT_CONFIG = "sargo" # "a3y17lte" # "sargo" diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index 0998ab59..50dc179b 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -139,9 +139,7 @@ def adb_twrp_copy_partitions(bin_path: Path, config_path: Path): return True -def adb_twrp_wipe_and_install( - bin_path: Path, target: str, config_path: Path -) -> bool: +def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> bool: """Wipe and format data with twrp, then flash os image with adb. Only works for twrp recovery. diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index fe1caf87..9cbf36a8 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -202,7 +202,9 @@ def call_to_phone(self, e, command: str): target=self.state.image_path, config_path=self.state.config_path, ), - "adb_twrp_copy_partitions": partial(adb_twrp_copy_partitions, config_path=self.state.config_path), + "adb_twrp_copy_partitions": partial( + adb_twrp_copy_partitions, config_path=self.state.config_path + ), "fastboot_unlock": fastboot_unlock, "fastboot_unlock_with_code": partial( fastboot_unlock_with_code, unlock_code=self.inputtext.value From 52596e5039c739cadf3ac107c42f31666afb9231 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 15:58:47 +0100 Subject: [PATCH 29/35] Add more requirements to configs and update REadme --- README.md | 8 ++++---- openandroidinstaller/assets/configs/barbet.yaml | 2 ++ openandroidinstaller/assets/configs/beyond1lte.yaml | 2 ++ openandroidinstaller/assets/configs/flame.yaml | 2 ++ openandroidinstaller/assets/configs/redfin.yaml | 2 ++ openandroidinstaller/assets/configs/sunfish.yaml | 2 ++ 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a79c0165..fc0397db 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,12 @@ Samsung | Galaxy A5 2016 | [a5xelte](https://wiki.lineageos.org/devices/a5xelte/ Samsung | Galaxy A7 2016 | a7xelte | | tested Samsung | Galaxy S7 | [herolte](https://wiki.lineageos.org/devices/herolte/) | SM-G930F | tested Samsung | Galaxy S9 | [starlte](https://wiki.lineageos.org/devices/starlte/) | | tested -Samsung | Galaxy S10 | beyond1lte | | tested +Samsung | Galaxy S10 | [beyond1lte](https://wiki.lineageos.org/devices/beyond1lte/) | | tested Google | Pixel 3a | [sargo](https://wiki.lineageos.org/devices/sargo/) | sargo | tested Google | Pixel 4 | [flame](https://wiki.lineageos.org/devices/flame/) | flame | tested -Google | Pixel 4a | sunfish | sunfish | tested -Google | Pixel 5 | redfin | redfin | tested -Google | Pixel 5a | barbet | barbet | tested +Google | Pixel 4a | [sunfish](https://wiki.lineageos.org/devices/sunfish/) | sunfish | tested +Google | Pixel 5 | [redfin](https://wiki.lineageos.org/devices/redfin/) | redfin | tested +Google | Pixel 5a | [barbet](https://wiki.lineageos.org/devices/barbet/) | barbet | tested Sony | Xperia Z | [yuga](https://wiki.lineageos.org/devices/yuga/) | C6603 | tested Sony | Xperia Z3 | [z3](https://wiki.lineageos.org/devices/z3/) | | tested Sony | Xperia ZX | kagura | | planned diff --git a/openandroidinstaller/assets/configs/barbet.yaml b/openandroidinstaller/assets/configs/barbet.yaml index 48ef00fd..94b4dc9e 100644 --- a/openandroidinstaller/assets/configs/barbet.yaml +++ b/openandroidinstaller/assets/configs/barbet.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Pixel 5a devicecode: barbet +requirements: + android: 12.1.0 steps: unlock_bootloader: - type: confirm_button diff --git a/openandroidinstaller/assets/configs/beyond1lte.yaml b/openandroidinstaller/assets/configs/beyond1lte.yaml index a9d70421..57d9cb45 100644 --- a/openandroidinstaller/assets/configs/beyond1lte.yaml +++ b/openandroidinstaller/assets/configs/beyond1lte.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Samsung Galaxy S10 devicecode: beyond1lte +requirements: + android: 12 steps: unlock_bootloader: flash_recovery: diff --git a/openandroidinstaller/assets/configs/flame.yaml b/openandroidinstaller/assets/configs/flame.yaml index faa34707..225432e5 100644 --- a/openandroidinstaller/assets/configs/flame.yaml +++ b/openandroidinstaller/assets/configs/flame.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Pixel 4 devicecode: flame +requirements: + android: 12.1.0 steps: unlock_bootloader: - type: confirm_button diff --git a/openandroidinstaller/assets/configs/redfin.yaml b/openandroidinstaller/assets/configs/redfin.yaml index 77264bf3..bbf6807a 100644 --- a/openandroidinstaller/assets/configs/redfin.yaml +++ b/openandroidinstaller/assets/configs/redfin.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Pixel 5 devicecode: redfin +requirements: + android: 12.1.0 steps: unlock_bootloader: - type: confirm_button diff --git a/openandroidinstaller/assets/configs/sunfish.yaml b/openandroidinstaller/assets/configs/sunfish.yaml index d54a8d96..75d7b326 100644 --- a/openandroidinstaller/assets/configs/sunfish.yaml +++ b/openandroidinstaller/assets/configs/sunfish.yaml @@ -2,6 +2,8 @@ metadata: maintainer: Tobias Sterbak (tsterbak) devicename: Pixel 4a devicecode: sunfish +requirements: + android: 12.1.0 steps: unlock_bootloader: - type: confirm_button From 7a65066823934c460a3d94b2264e8c6d92bf1ada Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 28 Nov 2022 16:14:57 +0100 Subject: [PATCH 30/35] Add configs for Galaxy Note 9 and 10 --- README.md | 2 ++ .../assets/configs/crownlte.yaml | 32 +++++++++++++++++++ openandroidinstaller/assets/configs/d1.yaml | 32 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 openandroidinstaller/assets/configs/crownlte.yaml create mode 100644 openandroidinstaller/assets/configs/d1.yaml diff --git a/README.md b/README.md index fc0397db..66d178f9 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,9 @@ Samsung | Galaxy A5 2016 | [a5xelte](https://wiki.lineageos.org/devices/a5xelte/ Samsung | Galaxy A7 2016 | a7xelte | | tested Samsung | Galaxy S7 | [herolte](https://wiki.lineageos.org/devices/herolte/) | SM-G930F | tested Samsung | Galaxy S9 | [starlte](https://wiki.lineageos.org/devices/starlte/) | | tested +Samsung | Galaxy Note 9 | [crownlte](https://wiki.lineageos.org/devices/crownlte/) | | tested Samsung | Galaxy S10 | [beyond1lte](https://wiki.lineageos.org/devices/beyond1lte/) | | tested +Samsung | Galaxy Note 10 | [d1](https://wiki.lineageos.org/devices/d1/) | | tested Google | Pixel 3a | [sargo](https://wiki.lineageos.org/devices/sargo/) | sargo | tested Google | Pixel 4 | [flame](https://wiki.lineageos.org/devices/flame/) | flame | tested Google | Pixel 4a | [sunfish](https://wiki.lineageos.org/devices/sunfish/) | sunfish | tested diff --git a/openandroidinstaller/assets/configs/crownlte.yaml b/openandroidinstaller/assets/configs/crownlte.yaml new file mode 100644 index 00000000..5084849b --- /dev/null +++ b/openandroidinstaller/assets/configs/crownlte.yaml @@ -0,0 +1,32 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: Samsung Galaxy Note 9 + devicecode: crownlte +requirements: + android: 10 +steps: + unlock_bootloader: + flash_recovery: + - type: call_button + content: > + As a first step, you need to boot into the bootloader. A bootloader is the piece of software, + that tells your phone who to start and run an operating system (like Android). Your device should be turned on. + Then press 'Confirm and run' to reboot into the bootloader. Continue once it's done. + command: adb_reboot_download + - type: call_button + content: In this step, you need to flash a custom recovery on your device. Press 'Confirm and run' to start the process. Confirm afterwards to continue. + command: heimdall_flash_recovery + - type: confirm_button + img: samsung-buttons.png + content: > + Unplug the USB cable from your device. Then manually reboot into recovery by pressing the *Volume Down* + *Bixby* for 8~10 seconds + until the screen turns black & release the buttons immediately when it does, then boot to recovery with the device powered off, + hold *Volume Up* + *Bixby* + *Power button*. + install_os: + - type: call_button + content: > + In the next steps, you finally flash the selected OS image. + Connect your device with your computer with the USB-Cable. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/d1.yaml b/openandroidinstaller/assets/configs/d1.yaml new file mode 100644 index 00000000..60bde268 --- /dev/null +++ b/openandroidinstaller/assets/configs/d1.yaml @@ -0,0 +1,32 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: Samsung Galaxy Note 10 + devicecode: d1 +requirements: + android: 12 +steps: + unlock_bootloader: + flash_recovery: + - type: call_button + content: > + As a first step, you need to boot into the bootloader. A bootloader is the piece of software, + that tells your phone who to start and run an operating system (like Android). Your device should be turned on. + Then press 'Confirm and run' to reboot into the bootloader. Continue once it's done. + command: adb_reboot_download + - type: call_button + content: In this step, you need to flash a custom recovery on your device. Press 'Confirm and run' to start the process. Confirm afterwards to continue. + command: heimdall_flash_recovery + - type: confirm_button + img: samsung-buttons.png + content: > + Unplug the USB cable from your device. Then manually reboot into recovery by pressing the *Volume Down* + *Bixby* for 8~10 seconds + until the screen turns black & release the buttons immediately when it does, then boot to recovery with the device powered off, + hold *Volume Up* + *Bixby* + *Power button*. + install_os: + - type: call_button + content: > + In the next steps, you finally flash the selected OS image. + Connect your device with your computer with the USB-Cable. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file From 548cc59f93f302c62875f396ced13691525859c8 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 29 Nov 2022 08:58:06 +0100 Subject: [PATCH 31/35] Make device code casesensitive in download link --- openandroidinstaller/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index 3fe91c33..9ed027a0 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -22,7 +22,7 @@ def get_download_link(devicecode: str) -> Optional[str]: """Check if a lineageOS version for this device exists on download.lineageos.com and return the respective download link.""" - url = f"https://download.lineageos.org/{devicecode.lower()}" + url = f"https://download.lineageos.org/{devicecode}" try: logger.info(f"Checking {url}") # Get Url From 2d10dd4e405b71ba5e5f7483d9ffe47b63770908 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 29 Nov 2022 12:35:25 +0100 Subject: [PATCH 32/35] Add configs for OnePlus 6, 6T, 7, 7 Pro, 7T, 7T Pro, Nord N200 --- README.md | 10 ++-- .../assets/configs/cedric.yaml | 2 +- openandroidinstaller/assets/configs/dre.yaml | 53 +++++++++++++++++++ .../assets/configs/enchilada.yaml | 53 +++++++++++++++++++ .../assets/configs/fajita.yaml | 53 +++++++++++++++++++ .../assets/configs/guacamole.yaml | 44 +++++++++++++++ .../assets/configs/guacamoleb.yaml | 44 +++++++++++++++ .../assets/configs/hotdog.yaml | 44 +++++++++++++++ .../assets/configs/hotdogb.yaml | 44 +++++++++++++++ .../assets/configs/ocean.yaml | 2 +- 10 files changed, 344 insertions(+), 5 deletions(-) create mode 100644 openandroidinstaller/assets/configs/dre.yaml create mode 100644 openandroidinstaller/assets/configs/enchilada.yaml create mode 100644 openandroidinstaller/assets/configs/fajita.yaml create mode 100644 openandroidinstaller/assets/configs/guacamole.yaml create mode 100644 openandroidinstaller/assets/configs/guacamoleb.yaml create mode 100644 openandroidinstaller/assets/configs/hotdog.yaml create mode 100644 openandroidinstaller/assets/configs/hotdogb.yaml diff --git a/README.md b/README.md index 66d178f9..c3e90321 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,13 @@ Fairphone | Fairphone 2 | [FP2](https://wiki.lineageos.org/devices/FP2/) | | tes Fairphone | Fairphone 3 | [FP3](https://wiki.lineageos.org/devices/FP3/) | | tested Motorola | moto G5 | [cedric](https://wiki.lineageos.org/devices/cedric/) | | tested Motorola | moto g7 power | [ocean](https://wiki.lineageos.org/devices/ocean/) | | tested -OnePlus | 6 | enchilada | | under development -OnePlus | 6T | fajita | | under development -OnePlus | 7T | hotdogb | | under development +OnePlus | 6 | [enchilada](https://wiki.lineageos.org/devices/enchilada/) | | tested +OnePlus | 6T | [fajita](https://wiki.lineageos.org/devices/fajita/) | | tested +OnePlus | 7 | [guacamoleb](https://wiki.lineageos.org/devices/guacamoleb/) | | tested +OnePlus | 7 Pro | [guacamole](https://wiki.lineageos.org/devices/guacamole/) | | tested +OnePlus | 7T | [hotdogb](https://wiki.lineageos.org/devices/hotdogb/) | | tested +OnePlus | 7T Pro | [hotdog](https://wiki.lineageos.org/devices/hotdog/) | | tested +OnePlus | Nord N200 | [dre](https://wiki.lineageos.org/devices/dre/) | | tested OnePlus | 9 | lemonade | | under development diff --git a/openandroidinstaller/assets/configs/cedric.yaml b/openandroidinstaller/assets/configs/cedric.yaml index 8d2ed852..2f391028 100644 --- a/openandroidinstaller/assets/configs/cedric.yaml +++ b/openandroidinstaller/assets/configs/cedric.yaml @@ -1,6 +1,6 @@ metadata: maintainer: Tobias Sterbak (tsterbak) - devicename: Moto G5 + devicename: Motorola Moto G5 devicecode: cedric steps: unlock_bootloader: diff --git a/openandroidinstaller/assets/configs/dre.yaml b/openandroidinstaller/assets/configs/dre.yaml new file mode 100644 index 00000000..dc4858ab --- /dev/null +++ b/openandroidinstaller/assets/configs/dre.yaml @@ -0,0 +1,53 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: OnePlus Nord N200 + devicecode: dre +requirements: + android: 11 +steps: + unlock_bootloader: + - type: call_button + content: > + As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). Your device should be turned on. + Press 'Confirm and run' to reboot into the bootloader. + command: adb_reboot_bootloader + - type: call_button + content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. + command: fastboot_oem_unlock + - type: confirm_button + content: > + At this point the device may display on-screen prompts which will require interaction to continue the process of unlocking the bootloader. + Please take whatever actions the device asks you to to proceed. + - type: call_button + content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. + command: fastboot_reboot + - type: confirm_button + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + - type: call_button + command: adb_twrp_copy_partitions + content: > + In some cases, the inactive slot can be unpopulated or contain much older firmware than the active slot, leading to various issues including a potential hard-brick. + We can ensure none of that will happen by copying the contents of the active slot to the inactive slot. Press 'confirm and run' to to this. Once you are in the bootloader again, continue. + - type: call_button + command: fastboot_flash_recovery + content: > + Now we need to boot into recovery again. Press run and when you see the TWRP screen you can continue. + install_os: + - type: call_button + content: > + In this last step, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/enchilada.yaml b/openandroidinstaller/assets/configs/enchilada.yaml new file mode 100644 index 00000000..a55bcb32 --- /dev/null +++ b/openandroidinstaller/assets/configs/enchilada.yaml @@ -0,0 +1,53 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: OnePlus 6 + devicecode: enchilada +requirements: + android: 11 +steps: + unlock_bootloader: + - type: call_button + content: > + As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). Your device should be turned on. + Press 'Confirm and run' to reboot into the bootloader. + command: adb_reboot_bootloader + - type: call_button + content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. + command: fastboot_oem_unlock + - type: confirm_button + content: > + At this point the device may display on-screen prompts which will require interaction to continue the process of unlocking the bootloader. + Please take whatever actions the device asks you to to proceed. + - type: call_button + content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. + command: fastboot_reboot + - type: confirm_button + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + - type: call_button + command: adb_twrp_copy_partitions + content: > + In some cases, the inactive slot can be unpopulated or contain much older firmware than the active slot, leading to various issues including a potential hard-brick. + We can ensure none of that will happen by copying the contents of the active slot to the inactive slot. Press 'confirm and run' to to this. Once you are in the bootloader again, continue. + - type: call_button + command: fastboot_flash_recovery + content: > + Now we need to boot into recovery again. Press run and when you see the TWRP screen you can continue. + install_os: + - type: call_button + content: > + In this last step, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/fajita.yaml b/openandroidinstaller/assets/configs/fajita.yaml new file mode 100644 index 00000000..1660ed4f --- /dev/null +++ b/openandroidinstaller/assets/configs/fajita.yaml @@ -0,0 +1,53 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: OnePlus 6T + devicecode: fajita +requirements: + android: 11 +steps: + unlock_bootloader: + - type: call_button + content: > + As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). Your device should be turned on. + Press 'Confirm and run' to reboot into the bootloader. + command: adb_reboot_bootloader + - type: call_button + content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. + command: fastboot_oem_unlock + - type: confirm_button + content: > + At this point the device may display on-screen prompts which will require interaction to continue the process of unlocking the bootloader. + Please take whatever actions the device asks you to to proceed. + - type: call_button + content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. + command: fastboot_reboot + - type: confirm_button + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + - type: call_button + command: adb_twrp_copy_partitions + content: > + In some cases, the inactive slot can be unpopulated or contain much older firmware than the active slot, leading to various issues including a potential hard-brick. + We can ensure none of that will happen by copying the contents of the active slot to the inactive slot. Press 'confirm and run' to to this. Once you are in the bootloader again, continue. + - type: call_button + command: fastboot_flash_recovery + content: > + Now we need to boot into recovery again. Press run and when you see the TWRP screen you can continue. + install_os: + - type: call_button + content: > + In this last step, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/guacamole.yaml b/openandroidinstaller/assets/configs/guacamole.yaml new file mode 100644 index 00000000..a85c5e6e --- /dev/null +++ b/openandroidinstaller/assets/configs/guacamole.yaml @@ -0,0 +1,44 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: OnePlus 7 Pro + devicecode: guacamole +requirements: + android: 12 +steps: + unlock_bootloader: + - type: call_button + content: > + As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). Your device should be turned on. + Press 'Confirm and run' to reboot into the bootloader. + command: adb_reboot_bootloader + - type: call_button + content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. + command: fastboot_oem_unlock + - type: confirm_button + content: > + At this point the device may display on-screen prompts which will require interaction to continue the process of unlocking the bootloader. + Please take whatever actions the device asks you to to proceed. + - type: call_button + content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. + command: fastboot_reboot + - type: confirm_button + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + install_os: + - type: call_button + content: > + In the next steps, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/guacamoleb.yaml b/openandroidinstaller/assets/configs/guacamoleb.yaml new file mode 100644 index 00000000..8c8f35d0 --- /dev/null +++ b/openandroidinstaller/assets/configs/guacamoleb.yaml @@ -0,0 +1,44 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: OnePlus 7 + devicecode: guacamoleb +requirements: + android: 12 +steps: + unlock_bootloader: + - type: call_button + content: > + As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). Your device should be turned on. + Press 'Confirm and run' to reboot into the bootloader. + command: adb_reboot_bootloader + - type: call_button + content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. + command: fastboot_oem_unlock + - type: confirm_button + content: > + At this point the device may display on-screen prompts which will require interaction to continue the process of unlocking the bootloader. + Please take whatever actions the device asks you to to proceed. + - type: call_button + content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. + command: fastboot_reboot + - type: confirm_button + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + install_os: + - type: call_button + content: > + In the next steps, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/hotdog.yaml b/openandroidinstaller/assets/configs/hotdog.yaml new file mode 100644 index 00000000..763b8bc0 --- /dev/null +++ b/openandroidinstaller/assets/configs/hotdog.yaml @@ -0,0 +1,44 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: OnePlus 7T Pro + devicecode: hotdog +requirements: + android: 12 +steps: + unlock_bootloader: + - type: call_button + content: > + As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). Your device should be turned on. + Press 'Confirm and run' to reboot into the bootloader. + command: adb_reboot_bootloader + - type: call_button + content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. + command: fastboot_oem_unlock + - type: confirm_button + content: > + At this point the device may display on-screen prompts which will require interaction to continue the process of unlocking the bootloader. + Please take whatever actions the device asks you to to proceed. + - type: call_button + content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. + command: fastboot_reboot + - type: confirm_button + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + install_os: + - type: call_button + content: > + In the next steps, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/hotdogb.yaml b/openandroidinstaller/assets/configs/hotdogb.yaml new file mode 100644 index 00000000..879e81e1 --- /dev/null +++ b/openandroidinstaller/assets/configs/hotdogb.yaml @@ -0,0 +1,44 @@ +metadata: + maintainer: Tobias Sterbak (tsterbak) + devicename: OnePlus 7T + devicecode: hotdogb +requirements: + android: 12 +steps: + unlock_bootloader: + - type: call_button + content: > + As a first step, you need to unlock the bootloader. A bootloader is the piece of software, that tells your phone + how to start and run an operating system (like Android). Your device should be turned on. + Press 'Confirm and run' to reboot into the bootloader. + command: adb_reboot_bootloader + - type: call_button + content: In this step you actually unlock the bootloader. Just press 'Confirm and run' here. Once it's done, press continue here. + command: fastboot_oem_unlock + - type: confirm_button + content: > + At this point the device may display on-screen prompts which will require interaction to continue the process of unlocking the bootloader. + Please take whatever actions the device asks you to to proceed. + - type: call_button + content: To finish the unlocking, the phone needs to reboot. Just press 'Confirm and run' here to reboot. Then continue. + command: fastboot_reboot + - type: confirm_button + content: The bootloader is now unlocked. Since the device resets completely, you will need to re-enable Developer Options and USB debugging to continue. + flash_recovery: + - type: call_button + content: > + Now you need to flash a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + Make sure your device is turned on. You need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + install_os: + - type: call_button + content: > + In the next steps, you finally flash the selected OS image. + Wait until the TWRP screen appears. Then run the command. + This step will format your phone and wipe all the data. It will also remove encryption and delete all files stored + in the internal storage. Then the OS image will be installed. Confirm to run. This might take a while. At the end your phone will boot into the new OS. + command: adb_twrp_wipe_and_install \ No newline at end of file diff --git a/openandroidinstaller/assets/configs/ocean.yaml b/openandroidinstaller/assets/configs/ocean.yaml index 0aaf796c..ee5df871 100644 --- a/openandroidinstaller/assets/configs/ocean.yaml +++ b/openandroidinstaller/assets/configs/ocean.yaml @@ -1,6 +1,6 @@ metadata: maintainer: Tobias Sterbak (tsterbak) - devicename: Moto G7 power + devicename: Motorola Moto G7 power devicecode: ocean steps: unlock_bootloader: From 761ed5301dbac1d767c8c54350be2925fb73506c Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 29 Nov 2022 13:28:33 +0100 Subject: [PATCH 33/35] Fix sideloading paths to proper pathlib Paths --- openandroidinstaller/tooling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index 50dc179b..5a4ae48f 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -118,7 +118,7 @@ def adb_twrp_copy_partitions(bin_path: Path, config_path: Path): "adb", [ "sideload", - str(config_path.parent) + "/copy-partitions-20220613-signed.zip", + f"{config_path.parent.joinpath(Path('copy-partitions-20220613-signed.zip'))}", ], bin_path, ): @@ -192,7 +192,7 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path) -> # TODO: if this fails, a fix can be to just sideload something and then adb reboot for line in run_command( "adb", - ["sideload", str(config_path.parent) + "/helper.txt"], + ["sideload", f"{config_path.parent.joinpath(Path('helper.txt'))}"], bin_path, ): yield line From beb68b85a34709e3d245b38a3e5bca2c9d92c893 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 30 Nov 2022 11:31:42 +0100 Subject: [PATCH 34/35] fix build paths --- scripts/build.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/build.py b/scripts/build.py index 2fe72d40..267da642 100644 --- a/scripts/build.py +++ b/scripts/build.py @@ -33,6 +33,7 @@ def build_linux(): "--windowed", "--clean", "--icon=openandroidinstaller/assets/favicon.ico", + "--paths=openandroidinstaller", ] + added_data logger.info(f"Running pyinstaller with: {' '.join(pyinstaller_options)}") @@ -54,6 +55,7 @@ def build_macos(): "--windowed", "--clean", "--icon=openandroidinstaller/assets/favicon.ico", + "--paths=openandroidinstaller", ] + added_data logger.info(f"Running pyinstaller with: {' '.join(pyinstaller_options)}") @@ -76,6 +78,7 @@ def build_windows(): "--windowed", "--clean", "--icon=openandroidinstaller/assets/favicon.ico", + "--paths=openandroidinstaller", ] + added_data logger.info(f"Running pyinstaller with: {' '.join(pyinstaller_options)}") From 7d6e722ecf8b77dced147b11fd7fd5c362ff7e6b Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 30 Nov 2022 11:52:56 +0100 Subject: [PATCH 35/35] Fix closing the window --- openandroidinstaller/views/success_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openandroidinstaller/views/success_view.py b/openandroidinstaller/views/success_view.py index e62b55a9..5b1df974 100644 --- a/openandroidinstaller/views/success_view.py +++ b/openandroidinstaller/views/success_view.py @@ -32,7 +32,7 @@ def __init__(self, state: AppState): def build( self, ): - def close_window(): + def close_window(e): logger.success("Success! Close the window.") self.page.window_close()