From 0604ff11002697ce00b8e19d781bc7700a33994e Mon Sep 17 00:00:00 2001 From: rudu <40572253+anon1892@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:03:59 +0200 Subject: [PATCH] Add dtbo, vbmeta, super_empty flashing --- README.md | 3 +- openandroidinstaller/app_state.py | 3 + openandroidinstaller/installer_config.py | 6 +- openandroidinstaller/tooling.py | 51 +++++++ openandroidinstaller/views/select_view.py | 170 ++++++++++++++++++++++ openandroidinstaller/views/step_view.py | 8 + 6 files changed, 239 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a49b1a4a..622e9ed5 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,7 @@ Every config file should have `metadata` with the following fields: - `device_code`: str; The official device code. - `supported_device_codes`: List[str]; A list of supported device codes for the config. The config will be loaded based on this field. - `twrp-link`: [OPTIONAL] str; name of the corresponding twrp page. +- `additional_steps` : [OPTIONAL] List[str]; A list of additional steps. Could be `dtbo`, `vbmeta`, `super_empty`. In addition to these metadata, every config can have optional `requirements`. If these are set, the user is asked to check if they are meet. - `android`: [OPTIONAL] int|str; Android version to install prior to installing a custom ROM. @@ -231,7 +232,7 @@ Every step in the config file corresponds to one view in the application. These - `img`: [OPTIONAL] Display an image on the left pane of the step view. Images are loaded from `openandroidinstaller/assets/imgs/`. - `content`: str; The content text displayed alongside the action of the step. Used to inform the user about what's going on. For consistency and better readability the text should be moved into the next line using `>`. - `link`: [OPTIONAL] Link to use for the link button if type is `link_button_with_confirm`. -- `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_boot_recovery`, `fastboot_unlock_with_code`, `fastboot_unlock`, `fastboot_oem_unlock`, `fastboot_get_unlock_data`, `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_boot_recovery`, `fastboot_flash_additional_partitions`, `fastboot_unlock_with_code`, `fastboot_unlock`, `fastboot_oem_unlock`, `fastboot_get_unlock_data`, `fastboot_reboot`, `heimdall_flash_recovery`. - `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. **Please try to retain this order of these fields in your config to ensure consistency.** diff --git a/openandroidinstaller/app_state.py b/openandroidinstaller/app_state.py index eee468a9..3fa621a3 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -44,6 +44,9 @@ def __init__( self.config = None self.image_path = None self.recovery_path = None + self.dtbo_path = None + self.vbmeta_path = None + self.super_empty_path = None # store views self.default_views: List = [] diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 2a857d83..c7b68a77 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -62,6 +62,7 @@ def __init__( self.requirements = requirements self.device_code = metadata.get("device_code") self.is_ab = metadata.get("is_ab_device", False) + self.additional_steps = metadata.get("additional_steps") self.supported_device_codes = metadata.get("supported_device_codes") self.twrp_link = metadata.get("twrp-link") @@ -134,6 +135,8 @@ def _load_config(device_code: str, config_path: Path) -> Optional[InstallerConfi config = InstallerConfig.from_file(path) logger.info(f"Loaded device config from {path}.") if config: + if "additional_steps" not in config.metadata: + config.metadata.update({"additional_steps": "[]"}) logger.info(f"Config metadata: {config.metadata}.") return config else: @@ -150,7 +153,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_boot_recovery|fastboot_flash_boot|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_boot_recovery|fastboot_flash_boot|fastboot_unlock_with_code|fastboot_get_unlock_data|fastboot_unlock|fastboot_oem_unlock|fastboot_reboot|heimdall_flash_recovery|fastboot_flash_additional_partitions" ), schema.Optional("allow_skip"): bool, schema.Optional("img"): str, @@ -166,6 +169,7 @@ def validate_config(config: str) -> bool: "device_code": str, "supported_device_codes": [str], schema.Optional("twrp-link"): str, + schema.Optional("additional_steps"): [str], }, schema.Optional("requirements"): { schema.Optional("android"): schema.Or(str, int), diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index d3a551be..2b0d1f98 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -497,3 +497,54 @@ def search_device(platform: str, bin_path: Path) -> Optional[str]: except CalledProcessError: logger.error("Failed to detect a device.") return None + + +@add_logging("Flash additional partitions with fastboot") +def fastboot_flash_additional_partitions( + bin_path: Path, dtbo: str, vbmeta: str, super_empty: str, is_ab: bool = True +) -> TerminalResponse: + """Flash additional partitions (dtbo, vbmeta, super_empty) with fastboot.""" + if dtbo: + for line in run_command( + "fastboot flash dtbo ", target=f"{dtbo}", bin_path=bin_path + ): + yield line + if not is_ab: + if (type(line) == bool) and not line: + logger.error("Flashing dtbo failed.") + yield False + else: + yield True + else: + logger.info("No dtbo selected. Skipping") + yield True + + if vbmeta: + for line in run_command( + "fastboot flash vbmeta ", target=f"{vbmeta}", bin_path=bin_path + ): + yield line + if not is_ab: + if (type(line) == bool) and not line: + logger.error("Flashing vbmeta failed.") + yield False + else: + yield True + else: + logger.info("No vbmeta selected. Skipping") + yield True + + if super_empty: + for line in run_command( + "fastboot wipe-super ", target=f"{super_empty}", bin_path=bin_path + ): + yield line + if not is_ab: + if (type(line) == bool) and not line: + logger.error("Wiping super failed.") + yield False + else: + yield True + else: + logger.info("No super_empty selected. Skipping") + yield True diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 442fa98b..a4c1aaec 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -95,14 +95,26 @@ def init_visuals( # 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.pick_dtbo_dialog = FilePicker(on_result=self.pick_dtbo_result) + self.pick_vbmeta_dialog = FilePicker(on_result=self.pick_vbmeta_result) + self.pick_super_empty_dialog = FilePicker( + on_result=self.pick_super_empty_result + ) + self.selected_image = Text("Selected image: ") self.selected_recovery = Text("Selected recovery: ") + self.selected_dtbo = Text("Selected dtbo: ") + self.selected_vbmeta = Text("Selected vbmeta: ") + self.selected_super_empty = Text("Selected super_empty: ") # 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 + self.pick_dtbo_dialog.on_result = self.enable_button_if_ready + self.pick_vbmeta_dialog.on_result = self.enable_button_if_ready + self.pick_super_empty_dialog.on_result = self.enable_button_if_ready # back button self.back_button = ElevatedButton( "Back", @@ -122,6 +134,9 @@ def build(self): # attach hidden dialogues self.right_view.controls.append(self.pick_image_dialog) self.right_view.controls.append(self.pick_recovery_dialog) + self.right_view.controls.append(self.pick_dtbo_dialog) + self.right_view.controls.append(self.pick_vbmeta_dialog) + self.right_view.controls.append(self.pick_super_empty_dialog) # create help/info button to show the help dialog info_button = OutlinedButton( @@ -226,6 +241,83 @@ def build(self): ), self.selected_recovery, Divider(), + ] + ) + + # attach the controls for uploading others partitions, like dtbo, vbmeta & super_empty + if "dtbo" in self.state.config.metadata["additional_steps"]: + self.right_view.controls.extend( + [ + Text("Select other specific images:", style="titleSmall"), + Markdown( + """ + Depending of the ROM, OpenAndroidInstaller may have to install additional images. + These images are usually needed for Android 13 ROM. + Make sure the file is for **your exact phone model!** + """ + ), + Row( + [ + FilledButton( + "Pick `dtbo.img` image", + icon=icons.UPLOAD_FILE, + on_click=lambda _: self.pick_dtbo_dialog.pick_files( + allow_multiple=False, + file_type="custom", + allowed_extensions=["img"], + ), + expand=True, + ), + ] + ), + self.selected_dtbo, + ] + ) + if "vbmeta" in self.state.config.metadata["additional_steps"]: + self.right_view.controls.extend( + [ + Row( + [ + FilledButton( + "Pick `vbmeta.img` image", + icon=icons.UPLOAD_FILE, + on_click=lambda _: self.pick_vbmeta_dialog.pick_files( + allow_multiple=False, + file_type="custom", + allowed_extensions=["img"], + ), + expand=True, + ), + ] + ), + self.selected_vbmeta, + ] + ) + if "super_empty" in self.state.config.metadata["additional_steps"]: + self.right_view.controls.extend( + [ + Row( + [ + FilledButton( + "Pick `super_empty.img` image", + icon=icons.UPLOAD_FILE, + on_click=lambda _: self.pick_super_empty_dialog.pick_files( + allow_multiple=False, + file_type="custom", + allowed_extensions=["img"], + ), + expand=True, + ), + ] + ), + self.selected_super_empty, + Divider(), + ] + ) + + # attach the bottom buttons + self.right_view.controls.extend( + [ self.info_field, Row([self.back_button, self.confirm_button]), ] @@ -291,6 +383,61 @@ def pick_recovery_result(self, e: FilePickerResultEvent): # update self.selected_recovery.update() + def pick_dtbo_result(self, e: FilePickerResultEvent): + path = ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" + # update the textfield with the name of the file + self.selected_dtbo.value = self.selected_dtbo.value.split(":")[0] + f": {path}" + if e.files: + # check if the dtbo works with the device and show the filename in different colors accordingly + if path == "dtbo.img": + self.selected_dtbo.color = colors.GREEN + self.state.dtbo_path = e.files[0].path + logger.info(f"Selected dtbo from {self.state.dtbo_path}") + else: + self.selected_dtbo.color = colors.RED + else: + logger.info("No image selected.") + # update + self.selected_dtbo.update() + + def pick_vbmeta_result(self, e: FilePickerResultEvent): + path = ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" + # update the textfield with the name of the file + self.selected_vbmeta.value = ( + self.selected_vbmeta.value.split(":")[0] + f": {path}" + ) + if e.files: + # check if the vbmeta works with the device and show the filename in different colors accordingly + if path == "vbmeta.img": + self.selected_vbmeta.color = colors.GREEN + self.state.vbmeta_path = e.files[0].path + logger.info(f"Selected vbmeta from {self.state.vbmeta_path}") + else: + self.selected_vbmeta.color = colors.RED + else: + logger.info("No image selected.") + # update + self.selected_vbmeta.update() + + def pick_super_empty_result(self, e: FilePickerResultEvent): + path = ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" + # update the textfield with the name of the file + self.selected_super_empty.value = ( + self.selected_super_empty.value.split(":")[0] + f": {path}" + ) + if e.files: + # check if the super_empty works with the device and show the filename in different colors accordingly + if path == "super_empty.img": + self.selected_super_empty.color = colors.GREEN + self.state.super_empty_path = e.files[0].path + logger.info(f"Selected super_empty from {self.state.super_empty_path}") + else: + self.selected_super_empty.color = colors.RED + else: + logger.info("No image selected.") + # update + self.selected_super_empty.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 ( @@ -320,6 +467,29 @@ def enable_button_if_ready(self, e): self.confirm_button.disabled = True self.right_view.update() return + + if ( + (self.selected_dtbo.color and self.selected_dtbo.color == "red") + or (self.selected_vbmeta.color and self.selected_vbmeta.color == "red") + or ( + self.selected_super_empty.color + and self.selected_super_empty.color == "red" + ) + ): + logger.error( + "Some additional images don't match. Please select different ones." + ) + self.info_field.controls = [ + Text( + "Some additional images don't match. Select right ones or unselect them.", + color=colors.RED, + weight="bold", + ) + ] + self.confirm_button.disabled = True + 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 diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 1e4dffbf..969197c7 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -45,6 +45,7 @@ adb_twrp_copy_partitions, fastboot_boot_recovery, fastboot_flash_boot, + fastboot_flash_additional_partitions, fastboot_oem_unlock, fastboot_reboot, fastboot_unlock, @@ -231,6 +232,13 @@ def call_to_phone(self, e, command: str): fastboot_flash_boot, recovery=self.state.recovery_path, ), + "fastboot_flash_additional_partitions": partial( + fastboot_flash_additional_partitions, + dtbo=self.state.dtbo_path, + vbmeta=self.state.vbmeta_path, + super_empty=self.state.super_empty_path, + is_ab=self.state.config.is_ab, + ), "fastboot_reboot": fastboot_reboot, "heimdall_flash_recovery": partial( heimdall_flash_recovery, recovery=self.state.recovery_path