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 01/25] 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 From 62be5c351fb79d87ca6b51be03f8cbfbc652b25d Mon Sep 17 00:00:00 2001 From: rudu <40572253+anon1892@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:27:27 +0200 Subject: [PATCH 02/25] Added device specific notes --- README.md | 1 + openandroidinstaller/installer_config.py | 1 + openandroidinstaller/views/select_view.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+) diff --git a/README.md b/README.md index a49b1a4a..bea6ab57 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. +- `notes`: [OPTIONAL] str; specific phone information, showed before choosing ROM / recovery 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. diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 2a857d83..cf41617a 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -166,6 +166,7 @@ def validate_config(config: str) -> bool: "device_code": str, "supported_device_codes": [str], schema.Optional("twrp-link"): str, + schema.Optional("notes"): str, }, schema.Optional("requirements"): { schema.Optional("android"): schema.Or(str, int), diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 442fa98b..a83ae0fe 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -144,6 +144,20 @@ def build(self): # text row to show infos during the process self.info_field = Row() + + # Device specific notes + if "notes" in self.state.config.metadata: + self.right_view.controls.extend( + [ + Text( + "Important notes for your device", + style="titleSmall", + color=colors.RED, + weight="bold", + ), + Markdown(f"""{self.state.config.metadata['notes']}"""), + ] + ) # if there is an available download, show the button to the page if self.download_link: twrp_download_link = f"https://dl.twrp.me/{self.state.config.twrp_link if self.state.config.twrp_link else self.state.config.device_code}" From 361f0a432e4a184647568e009bc1f200016ca515 Mon Sep 17 00:00:00 2001 From: rudu <40572253+anon1892@users.noreply.github.com> Date: Tue, 1 Aug 2023 15:09:02 +0200 Subject: [PATCH 03/25] Added Mi439 (Redmi 7A & co.) --- README.md | 7 ++ .../assets/configs/Mi439.yaml | 71 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 openandroidinstaller/assets/configs/Mi439.yaml diff --git a/README.md b/README.md index a49b1a4a..b836eb29 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,13 @@ OnePlus | Nord N200 | [dre](https://wiki.lineageos.org/devices/dre) | | tested OnePlus | 9 | lemonade | | under development +
Xiaomi + +Vendor | Device Name | CodeName | Models | Status +---|---|---|---|--- +Xiaomi | Redmi 7A / 8 / 8A / 8A Dual | [Mi439](https://wiki.lineageos.org/devices/Mi439) : pine / olive / olivelite / olivewood | | tested +
+ And more to come! diff --git a/openandroidinstaller/assets/configs/Mi439.yaml b/openandroidinstaller/assets/configs/Mi439.yaml new file mode 100644 index 00000000..1e706574 --- /dev/null +++ b/openandroidinstaller/assets/configs/Mi439.yaml @@ -0,0 +1,71 @@ +metadata: + maintainer: A non (anon) + device_name: Xiaomi Redmi 7A / 8 / 8A / 8A Dual + is_ab_device: false + device_code: Mi439 + supported_recovery: + - orangefox + - twrp + additional_steps: + - dtbo + - vbmeta + - super_empty + supported_device_codes: + - Mi439 + - pine + - olive + - olivelite + - olivewood + notes: > + - If something goes wrong, you can reinstall MiUI here : https://xiaomifirmwareupdater.com + + - Be careful when choosing OrangeFox version, Android 12 & 13 ROM needs OrangeFox version code with `A12`, for example `R11.1_5_A12`. Android 10 & 11 ROM needs OrangeFox version code without `A12` (bellow on the page) +requirements: + firmware: MiUI 12.5 (Q) +steps: + unlock_bootloader: + - type: confirm_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. This will reset your phone. + - type: link_button_with_confirm + content: > + - Create a Mi account on Xiaomi’s website. Beware that one account is only allowed to unlock one unique device every 30 days. + + - Add a phone number to your Mi account, insert a SIM into your phone. + + - Enable developer options in `Settings` > `About Phone` by repeatedly tapping MIUI Version. + + - Link the device to your Mi account in `Settings` > `Additional settings` > `Developer options` > `Mi Unlock status`. + + - Download the Mi Unlock app with the link bellow (Windows is required to run the app), and follow the instructions provided by the app. It may tell you that you have to wait, usually 7 days. If it does so, please wait the quoted amount of time before continuing to the next step! + + - After device and Mi account are successfully verified, the bootloader should be unlocked. + + - Since the device resets completely, you will need to re-enable USB debugging to continue : `Settings` > `Additional settings` > `Developer options` > `USB debugging` + link: https://en.miui.com/unlock/download_en.html + boot_recovery: + - type: call_button + content: > + Now you need to install 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. + + Once the device is fully booted, you need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: > + Install additional partitions selected before by pressing 'Confirm and run'. Once it's done continue. + + Note : If you have not selected this partition, it will do nothing. + command: fastboot_flash_additional_partitions + - type: call_button + content: > + Install the recovery you chosen before by pressing 'Confirm and run'. Once it's done continue. + command: fastboot_flash_recovery + - type: call_button + img: ofox.png + content: > + Reboot to recovery by pressing 'Confirm and run', and hold the Vol+ button of your phone UNTIL you see the recovery. + If MiUI starts, you have to start the process again, since MiUI delete the recovery you just flashed. + Once it's done continue. + command: fastboot_reboot_recovery \ No newline at end of file From 812bb51d9909c33f0b6e72ae7f0aedbb71e01f98 Mon Sep 17 00:00:00 2001 From: rudu <40572253+anon1892@users.noreply.github.com> Date: Wed, 2 Aug 2023 13:58:18 +0200 Subject: [PATCH 04/25] Update `notes`, add `brand` and `untested` metadata. --- README.md | 4 +++- openandroidinstaller/views/select_view.py | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bea6ab57..e20c44e9 100644 --- a/README.md +++ b/README.md @@ -210,12 +210,14 @@ A config file consists of two parts. The first part are some metadata about the ##### How to write Metadata Every config file should have `metadata` with the following fields: - `maintainer`: str; Maintainer and author of the config file. +- `brand`: [OPTIONAL] str; Name of the brand. Can be used to make brand specific actions. - `device_name`: str; Name of the device. - `is_ab_device`: bool; A boolean to determine if the device is a/b-partitioned or not. - `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. -- `notes`: [OPTIONAL] str; specific phone information, showed before choosing ROM / recovery +- `notes`: [OPTIONAL] List[str]; specific phone information, showed before choosing ROM / recovery +- `untested`: [OPTIONAL] bool; If `true`, a warning message is showed during installation process. 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. diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index a83ae0fe..19b24700 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -147,6 +147,13 @@ def build(self): # Device specific notes if "notes" in self.state.config.metadata: + notes = "" + if "brand" in self.state.config.metadata and (self.state.config.metadata['brand'] == "xiaomi" or self.state.config.metadata['brand'] == "poco"): + notes += "- If something goes wrong, you can reinstall MiUI here :\n\n\n" + if "untested" in self.state.config.metadata and self.state.config.metadata['untested'] == True: + notes += "- **This device has never been tested with OpenAndroidInstaller.** The installation can go wrong. You may have to finish the installation process with command line. If you test, please report the result on GitHub.\n\n" + for note in self.state.config.metadata['notes']: + notes += "- " + note + "\n\n" self.right_view.controls.extend( [ Text( @@ -155,7 +162,7 @@ def build(self): color=colors.RED, weight="bold", ), - Markdown(f"""{self.state.config.metadata['notes']}"""), + Markdown(f"""{notes}"""), ] ) # if there is an available download, show the button to the page From 48be05d5d745966f533c54e983c395b745f5bf9c Mon Sep 17 00:00:00 2001 From: rudu <40572253+anon1892@users.noreply.github.com> Date: Wed, 2 Aug 2023 19:12:58 +0200 Subject: [PATCH 05/25] Add `brand` metadata & update of `notes` --- openandroidinstaller/assets/configs/Mi439.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openandroidinstaller/assets/configs/Mi439.yaml b/openandroidinstaller/assets/configs/Mi439.yaml index 1e706574..57797c68 100644 --- a/openandroidinstaller/assets/configs/Mi439.yaml +++ b/openandroidinstaller/assets/configs/Mi439.yaml @@ -1,5 +1,6 @@ metadata: maintainer: A non (anon) + brand: xiaomi device_name: Xiaomi Redmi 7A / 8 / 8A / 8A Dual is_ab_device: false device_code: Mi439 @@ -16,9 +17,7 @@ metadata: - olive - olivelite - olivewood - notes: > - - If something goes wrong, you can reinstall MiUI here : https://xiaomifirmwareupdater.com - + notes: - Be careful when choosing OrangeFox version, Android 12 & 13 ROM needs OrangeFox version code with `A12`, for example `R11.1_5_A12`. Android 10 & 11 ROM needs OrangeFox version code without `A12` (bellow on the page) requirements: firmware: MiUI 12.5 (Q) From 616c7e6f73fe752e7d9851679663f165b729b023 Mon Sep 17 00:00:00 2001 From: rudu <40572253+anon1892@users.noreply.github.com> Date: Wed, 2 Aug 2023 19:54:55 +0200 Subject: [PATCH 06/25] Fix --- openandroidinstaller/views/select_view.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 19b24700..0c45502f 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -146,14 +146,16 @@ def build(self): self.info_field = Row() # Device specific notes + notes = "" + if "brand" in self.state.config.metadata and ( + self.state.config.metadata['brand'] == "xiaomi" or self.state.config.metadata['brand'] == "poco"): + notes += "- If something goes wrong, you can reinstall MiUI here :\n\n\n" + if "untested" in self.state.config.metadata and self.state.config.metadata['untested'] == True: + notes += "- **This device has never been tested with OpenAndroidInstaller.** The installation can go wrong. You may have to finish the installation process with command line. If you test, please report the result on GitHub.\n\n" if "notes" in self.state.config.metadata: - notes = "" - if "brand" in self.state.config.metadata and (self.state.config.metadata['brand'] == "xiaomi" or self.state.config.metadata['brand'] == "poco"): - notes += "- If something goes wrong, you can reinstall MiUI here :\n\n\n" - if "untested" in self.state.config.metadata and self.state.config.metadata['untested'] == True: - notes += "- **This device has never been tested with OpenAndroidInstaller.** The installation can go wrong. You may have to finish the installation process with command line. If you test, please report the result on GitHub.\n\n" for note in self.state.config.metadata['notes']: notes += "- " + note + "\n\n" + if notes != "": self.right_view.controls.extend( [ Text( From f46439634d2e3ed36dd9391abfcad50076595b8c Mon Sep 17 00:00:00 2001 From: rudu <40572253+anon1892@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:58:29 +0200 Subject: [PATCH 07/25] Add function to know image sdk (mainly Android version+20) --- openandroidinstaller/utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index 0c17baf5..47aefcaa 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -59,6 +59,20 @@ def image_works_with_device(supported_device_codes: List[str], image_path: str) ) return False +def image_sdk_level(image_path: str) -> int: + """ + Determine Android version of the selected image. + Android 13 : 33 + """ + with zipfile.ZipFile(image_path) as image_zip: + with image_zip.open( + "META-INF/com/android/metadata", mode="r" + ) as image_metadata: + metadata = image_metadata.readlines() + for line in metadata: + if b"sdk-level" in line: + return int(line[line.find(b'=')+1:-1].decode("utf-8")) + return 0 def recovery_works_with_device(device_code: str, recovery_path: str) -> bool: """Determine if a recovery works for the given device. From 661f9b44f724fc4ccbcc180ae364f536fd4b3afc Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 8 Aug 2023 19:02:10 +0200 Subject: [PATCH 08/25] Update the code for flashing additional partitions and improve the layout a bit --- ...te_your_own_installation_configurations.md | 3 +- openandroidinstaller/tooling.py | 103 ++++++++------- openandroidinstaller/utils.py | 7 +- openandroidinstaller/views/select_view.py | 118 ++++++++++-------- openandroidinstaller/views/start_view.py | 3 +- 5 files changed, 121 insertions(+), 113 deletions(-) diff --git a/doc/how_to_contribute_your_own_installation_configurations.md b/doc/how_to_contribute_your_own_installation_configurations.md index 1e481e70..c4167dc8 100644 --- a/doc/how_to_contribute_your_own_installation_configurations.md +++ b/doc/how_to_contribute_your_own_installation_configurations.md @@ -16,6 +16,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. Can be `dtbo`, `vbmeta`, `vendor_boot` or `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. @@ -32,7 +33,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_unlock_critical`, `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/tooling.py b/openandroidinstaller/tooling.py index 2b0d1f98..e3999c0b 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -416,7 +416,7 @@ def fastboot_boot_recovery( def fastboot_flash_boot(bin_path: Path, recovery: str) -> TerminalResponse: - """Temporarily, flash custom recovery with fastboot to boot partition.""" + """Flash custom recovery with fastboot to boot partition.""" logger.info("Flash custom recovery with fastboot.") for line in run_command( "fastboot flash boot", target=f"{recovery}", bin_path=bin_path @@ -440,6 +440,54 @@ def fastboot_flash_boot(bin_path: Path, recovery: str) -> TerminalResponse: yield True +@add_logging("Flash additional partitions with fastboot") +def fastboot_flash_additional_partitions( + bin_path: Path, dtbo: Optional[str], vbmeta: Optional[str], super_empty: Optional[str], is_ab: bool = True +) -> TerminalResponse: + """Flash additional partitions (dtbo, vbmeta, super_empty) with fastboot.""" + logger.info("Flash additional partitions with fastboot.") + if dtbo: + logger.info("dtbo selected. Flashing dtbo partition.") + 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: + yield True + + if vbmeta: + logger.info("vbmeta selected. Flashing vbmeta partition.") + 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 + + if super_empty: + logger.info("super_empty selected. Wiping super partition.") + 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 + + def heimdall_wait_for_download_available(bin_path: Path) -> bool: """Use heimdall detect to wait for download mode to become available on the device.""" logger.info("Wait for download mode to become available.") @@ -496,55 +544,4 @@ def search_device(platform: str, bin_path: Path) -> Optional[str]: return device_code 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 + return None \ No newline at end of file diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index 0b3e606a..c3539ae6 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -61,9 +61,10 @@ def image_works_with_device(supported_device_codes: List[str], image_path: str) def image_sdk_level(image_path: str) -> int: - """ - Determine Android version of the selected image. - Android 13 : 33 + """Determine Android version of the selected image. + + Example: + Android 13: 33 """ with zipfile.ZipFile(image_path) as image_zip: with image_zip.open( diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index a4c1aaec..5f17b6d3 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -30,6 +30,7 @@ AlertDialog, FilePicker, FilePickerResultEvent, + Checkbox, ) from flet_core.buttons import CountinuosRectangleBorder @@ -40,7 +41,7 @@ from views import BaseView from app_state import AppState from widgets import get_title, confirm_button -from utils import get_download_link, image_works_with_device, recovery_works_with_device +from utils import get_download_link, image_works_with_device, recovery_works_with_device, image_sdk_level class SelectFilesView(BaseView): @@ -103,9 +104,9 @@ def init_visuals( 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: ") + self.selected_dtbo = Checkbox(fill_color=colors.RED, value=None, disabled=True, tristate=True) + self.selected_vbmeta = Checkbox(fill_color=colors.RED, value=None, disabled=True, tristate=True) + self.selected_super_empty = Checkbox(fill_color=colors.RED, value=None, disabled=True, tristate=True) # initialize and manage button state. self.confirm_button = confirm_button(self.on_confirm) @@ -132,11 +133,13 @@ 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) + self.right_view.controls.extend([ + self.pick_image_dialog, + self.pick_recovery_dialog, + self.pick_dtbo_dialog, + self.pick_vbmeta_dialog, + self.pick_super_empty_dialog + ]) # create help/info button to show the help dialog info_button = OutlinedButton( @@ -159,6 +162,8 @@ def build(self): # text row to show infos during the process self.info_field = Row() + # column to insert the additional image selection controls if needed + self.additional_image_selection = Column() # if there is an available download, show the button to the page if self.download_link: twrp_download_link = f"https://dl.twrp.me/{self.state.config.twrp_link if self.state.config.twrp_link else self.state.config.device_code}" @@ -241,21 +246,36 @@ def build(self): ), self.selected_recovery, Divider(), + self.additional_image_selection, ] ) + # attach the bottom buttons + self.right_view.controls.extend( + [ + self.info_field, + Row([self.back_button, self.confirm_button]), + ] + ) + return self.view + + def toggle_additional_image_selection(self): + """Toggle the visibility of the additional image selection controls.""" # attach the controls for uploading others partitions, like dtbo, vbmeta & super_empty + additional_image_selection = [] + if self.state.config.metadata["additional_steps"]: + additional_image_selection.extend([ + Text("Select required additional images:", style="titleSmall"), + Markdown(""" +Your selected device and ROM requires flashing of additional partitions. Please select the required images below. + +Make sure the file is for **your exact phone model!**""", + ) + ]) if "dtbo" in self.state.config.metadata["additional_steps"]: - self.right_view.controls.extend( + self.selected_dtbo.value = False + additional_image_selection.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( @@ -268,13 +288,14 @@ def build(self): ), expand=True, ), + self.selected_dtbo, ] ), - self.selected_dtbo, ] ) if "vbmeta" in self.state.config.metadata["additional_steps"]: - self.right_view.controls.extend( + self.selected_vbmeta.value = False + additional_image_selection.extend( [ Row( [ @@ -288,13 +309,14 @@ def build(self): ), expand=True, ), + self.selected_vbmeta, ] ), - self.selected_vbmeta, ] ) if "super_empty" in self.state.config.metadata["additional_steps"]: - self.right_view.controls.extend( + self.selected_super_empty.value = False + additional_image_selection.extend( [ Row( [ @@ -308,21 +330,14 @@ def build(self): ), expand=True, ), + self.selected_super_empty, ] ), - self.selected_super_empty, Divider(), ] ) - - # attach the bottom buttons - self.right_view.controls.extend( - [ - self.info_field, - Row([self.back_button, self.confirm_button]), - ] - ) - return self.view + self.additional_image_selection.controls = additional_image_selection + self.additional_image_selection.update() def open_explain_images_dlg(self, e): """Open the dialog to explain OS and recovery image.""" @@ -356,6 +371,12 @@ def pick_image_result(self, e: FilePickerResultEvent): self.selected_image.color = colors.GREEN else: self.selected_image.color = colors.RED + # if the image works and the sdk level is 33 or higher, show the additional image selection + if self.selected_image.color == colors.GREEN and image_sdk_level(self.state.image_path) >= 33: + self.toggle_additional_image_selection() + else: + self.additional_image_selection.controls = [] + self.additional_image_selection.update() # update self.selected_image.update() @@ -385,16 +406,16 @@ def pick_recovery_result(self, e: FilePickerResultEvent): 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.selected_dtbo.fill_color = colors.GREEN + self.selected_dtbo.value = True 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 + self.selected_dtbo.fill_color = colors.RED + self.selected_dtbo.value = False else: logger.info("No image selected.") # update @@ -402,10 +423,6 @@ def pick_dtbo_result(self, e: FilePickerResultEvent): 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": @@ -422,17 +439,16 @@ def pick_vbmeta_result(self, e: FilePickerResultEvent): 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.selected_super_empty.fill_color = colors.GREEN + self.selected_super_empty.value = True 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 + self.selected_super_empty.fill_color = colors.RED + self.selected_super_empty.value = False else: logger.info("No image selected.") # update @@ -468,20 +484,14 @@ def enable_button_if_ready(self, e): 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" - ) - ): + # check if the additional images work with the device + if any(v == False for v in [self.selected_dtbo.value, self.selected_vbmeta.value, self.selected_super_empty.value]): 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.", + "Some additional images don't match. Please select the right ones.", color=colors.RED, weight="bold", ) diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py index ad729262..0a9245d5 100644 --- a/openandroidinstaller/views/start_view.py +++ b/openandroidinstaller/views/start_view.py @@ -206,8 +206,7 @@ def build(self): If you don't know what this means, you most likely don't need to do anything and you can just continue. """ ), - Row([self.bootloader_switch]), - Row([self.recovery_switch]), + Row([self.bootloader_switch, self.recovery_switch]), Divider(), self.device_infobox, Row( From 357002c3631d089a64f6a507985133aa189f9acd Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 10:21:58 +0200 Subject: [PATCH 09/25] Add fastboot_flash_recovery and fastboot_reboot_recovery from anon1892 --- openandroidinstaller/tooling.py | 35 ++++++++++++- openandroidinstaller/utils.py | 2 +- openandroidinstaller/views/select_view.py | 60 ++++++++++++++++------- openandroidinstaller/views/step_view.py | 8 +++ 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index e3999c0b..f7c23db2 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -440,9 +440,40 @@ def fastboot_flash_boot(bin_path: Path, recovery: str) -> TerminalResponse: yield True +@add_logging("Flash custom recovery with fastboot.") +def fastboot_flash_recovery( + bin_path: Path, recovery: str, is_ab: bool = True +) -> TerminalResponse: + """Flash custom recovery with fastboot.""" + for line in run_command( + "fastboot flash recovery ", target=f"{recovery}", bin_path=bin_path + ): + yield line + if not is_ab: + if (type(line) == bool) and not line: + logger.error("Flashing recovery failed.") + yield False + else: + yield True + + +@add_logging("Rebooting device to recovery.") +def fastboot_reboot_recovery(bin_path: Path) -> TerminalResponse: + """Reboot to recovery with fastboot. + + WARNING: On some devices, users need to press a specific key combo to make it work. + """ + for line in run_command("fastboot reboot recovery", bin_path): + yield line + + @add_logging("Flash additional partitions with fastboot") def fastboot_flash_additional_partitions( - bin_path: Path, dtbo: Optional[str], vbmeta: Optional[str], super_empty: Optional[str], is_ab: bool = True + bin_path: Path, + dtbo: Optional[str], + vbmeta: Optional[str], + super_empty: Optional[str], + is_ab: bool = True, ) -> TerminalResponse: """Flash additional partitions (dtbo, vbmeta, super_empty) with fastboot.""" logger.info("Flash additional partitions with fastboot.") @@ -544,4 +575,4 @@ def search_device(platform: str, bin_path: Path) -> Optional[str]: return device_code except CalledProcessError: logger.error("Failed to detect a device.") - return None \ No newline at end of file + return None diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index c3539ae6..42f2dccb 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -63,7 +63,7 @@ def image_works_with_device(supported_device_codes: List[str], image_path: str) def image_sdk_level(image_path: str) -> int: """Determine Android version of the selected image. - Example: + Example: Android 13: 33 """ with zipfile.ZipFile(image_path) as image_zip: diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 5f17b6d3..ef759809 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -41,7 +41,12 @@ from views import BaseView from app_state import AppState from widgets import get_title, confirm_button -from utils import get_download_link, image_works_with_device, recovery_works_with_device, image_sdk_level +from utils import ( + get_download_link, + image_works_with_device, + recovery_works_with_device, + image_sdk_level, +) class SelectFilesView(BaseView): @@ -104,9 +109,15 @@ def init_visuals( self.selected_image = Text("Selected image: ") self.selected_recovery = Text("Selected recovery: ") - self.selected_dtbo = Checkbox(fill_color=colors.RED, value=None, disabled=True, tristate=True) - self.selected_vbmeta = Checkbox(fill_color=colors.RED, value=None, disabled=True, tristate=True) - self.selected_super_empty = Checkbox(fill_color=colors.RED, value=None, disabled=True, tristate=True) + self.selected_dtbo = Checkbox( + fill_color=colors.RED, value=None, disabled=True, tristate=True + ) + self.selected_vbmeta = Checkbox( + fill_color=colors.RED, value=None, disabled=True, tristate=True + ) + self.selected_super_empty = Checkbox( + fill_color=colors.RED, value=None, disabled=True, tristate=True + ) # initialize and manage button state. self.confirm_button = confirm_button(self.on_confirm) @@ -133,13 +144,15 @@ def build(self): ) # attach hidden dialogues - self.right_view.controls.extend([ - self.pick_image_dialog, - self.pick_recovery_dialog, - self.pick_dtbo_dialog, - self.pick_vbmeta_dialog, - self.pick_super_empty_dialog - ]) + self.right_view.controls.extend( + [ + self.pick_image_dialog, + self.pick_recovery_dialog, + self.pick_dtbo_dialog, + self.pick_vbmeta_dialog, + self.pick_super_empty_dialog, + ] + ) # create help/info button to show the help dialog info_button = OutlinedButton( @@ -264,14 +277,17 @@ def toggle_additional_image_selection(self): # attach the controls for uploading others partitions, like dtbo, vbmeta & super_empty additional_image_selection = [] if self.state.config.metadata["additional_steps"]: - additional_image_selection.extend([ + additional_image_selection.extend( + [ Text("Select required additional images:", style="titleSmall"), - Markdown(""" + Markdown( + """ Your selected device and ROM requires flashing of additional partitions. Please select the required images below. Make sure the file is for **your exact phone model!**""", - ) - ]) + ), + ] + ) if "dtbo" in self.state.config.metadata["additional_steps"]: self.selected_dtbo.value = False additional_image_selection.extend( @@ -372,7 +388,10 @@ def pick_image_result(self, e: FilePickerResultEvent): else: self.selected_image.color = colors.RED # if the image works and the sdk level is 33 or higher, show the additional image selection - if self.selected_image.color == colors.GREEN and image_sdk_level(self.state.image_path) >= 33: + if ( + self.selected_image.color == colors.GREEN + and image_sdk_level(self.state.image_path) >= 33 + ): self.toggle_additional_image_selection() else: self.additional_image_selection.controls = [] @@ -485,7 +504,14 @@ def enable_button_if_ready(self, e): return # check if the additional images work with the device - if any(v == False for v in [self.selected_dtbo.value, self.selected_vbmeta.value, self.selected_super_empty.value]): + if any( + v == False + for v in [ + self.selected_dtbo.value, + self.selected_vbmeta.value, + self.selected_super_empty.value, + ] + ): logger.error( "Some additional images don't match. Please select different ones." ) diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 5824884f..9d431513 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -45,6 +45,8 @@ adb_twrp_copy_partitions, fastboot_boot_recovery, fastboot_flash_boot, + fastboot_flash_recovery, + fastboot_reboot_recovery, fastboot_flash_additional_partitions, fastboot_oem_unlock, fastboot_reboot, @@ -234,6 +236,12 @@ def call_to_phone(self, e, command: str): fastboot_flash_boot, recovery=self.state.recovery_path, ), + "fastboot_flash_recovery": partial( + fastboot_flash_recovery, + recovery=self.state.recovery_path, + is_ab=self.state.config.is_ab, + ), + "fastboot_reboot_recovery": fastboot_reboot_recovery, "fastboot_flash_additional_partitions": partial( fastboot_flash_additional_partitions, dtbo=self.state.dtbo_path, From 9ba8e215e29d327d353e11702f36cca2422a15a5 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 10:34:42 +0200 Subject: [PATCH 10/25] Fix merge issue --- openandroidinstaller/installer_config.py | 4 +++- openandroidinstaller/views/select_view.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 57393d85..77407a19 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -170,7 +170,9 @@ def validate_config(config: str) -> bool: "device_code": str, "supported_device_codes": [str], schema.Optional("twrp-link"): str, - schema.Optional("additional_steps"): Regex(r"dtbo|vbmeta|vendor_boot|super_empty"), + schema.Optional("additional_steps"): [ + Regex(r"dtbo|vbmeta|vendor_boot|super_empty") + ], schema.Optional("notes"): str, schema.Optional("brand"): str, }, diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 4a262d87..8a66bb0b 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -181,12 +181,17 @@ def build(self): # Device specific notes notes = "" if "brand" in self.state.config.metadata and ( - self.state.config.metadata['brand'] == "xiaomi" or self.state.config.metadata['brand'] == "poco"): + self.state.config.metadata["brand"] == "xiaomi" + or self.state.config.metadata["brand"] == "poco" + ): notes += "- If something goes wrong, you can reinstall MiUI here :\n\n\n" - if "untested" in self.state.config.metadata and self.state.config.metadata['untested'] == True: + if ( + "untested" in self.state.config.metadata + and self.state.config.metadata["untested"] == True + ): notes += "- **This device has never been tested with OpenAndroidInstaller.** The installation can go wrong. You may have to finish the installation process with command line. If you test, please report the result on GitHub.\n\n" if "notes" in self.state.config.metadata: - for note in self.state.config.metadata['notes']: + for note in self.state.config.metadata["notes"]: notes += "- " + note + "\n\n" if notes != "": self.right_view.controls.extend( From 554b75b64ebc039eab1df5e24d9591eecaf58a4f Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 10:39:00 +0200 Subject: [PATCH 11/25] Remove supported recoveries from Mi439 config for now --- openandroidinstaller/assets/configs/Mi439.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/openandroidinstaller/assets/configs/Mi439.yaml b/openandroidinstaller/assets/configs/Mi439.yaml index 57797c68..2ab5be21 100644 --- a/openandroidinstaller/assets/configs/Mi439.yaml +++ b/openandroidinstaller/assets/configs/Mi439.yaml @@ -4,9 +4,6 @@ metadata: device_name: Xiaomi Redmi 7A / 8 / 8A / 8A Dual is_ab_device: false device_code: Mi439 - supported_recovery: - - orangefox - - twrp additional_steps: - dtbo - vbmeta From 0bd2ccc3661e95b34b72d63f7980575401fe4c1b Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 10:40:58 +0200 Subject: [PATCH 12/25] Fix config validation for notes field --- openandroidinstaller/installer_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 77407a19..59419b0c 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -173,7 +173,7 @@ def validate_config(config: str) -> bool: schema.Optional("additional_steps"): [ Regex(r"dtbo|vbmeta|vendor_boot|super_empty") ], - schema.Optional("notes"): str, + schema.Optional("notes"): [str], schema.Optional("brand"): str, }, schema.Optional("requirements"): { From 13c85be1897f6e59f46f98b196842d64d1d5d9a3 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 10:55:11 +0200 Subject: [PATCH 13/25] Improve the code of the notes generation a bit --- openandroidinstaller/views/select_view.py | 41 ++++++++++++++--------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 8a66bb0b..43e5800f 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -179,21 +179,8 @@ def build(self): self.additional_image_selection = Column() # Device specific notes - notes = "" - if "brand" in self.state.config.metadata and ( - self.state.config.metadata["brand"] == "xiaomi" - or self.state.config.metadata["brand"] == "poco" - ): - notes += "- If something goes wrong, you can reinstall MiUI here :\n\n\n" - if ( - "untested" in self.state.config.metadata - and self.state.config.metadata["untested"] == True - ): - notes += "- **This device has never been tested with OpenAndroidInstaller.** The installation can go wrong. You may have to finish the installation process with command line. If you test, please report the result on GitHub.\n\n" - if "notes" in self.state.config.metadata: - for note in self.state.config.metadata["notes"]: - notes += "- " + note + "\n\n" - if notes != "": + notes = self.get_notes() + if notes: self.right_view.controls.extend( [ Text( @@ -202,9 +189,10 @@ def build(self): color=colors.RED, weight="bold", ), - Markdown(f"""{notes}"""), + Markdown(notes), ] ) + # if there is an available download, show the button to the page if self.download_link: twrp_download_link = f"https://dl.twrp.me/{self.state.config.twrp_link if self.state.config.twrp_link else self.state.config.device_code}" @@ -300,6 +288,27 @@ def build(self): ) return self.view + def get_notes(self) -> str: + """Prepare and get notes for the specific device from config.""" + notes = [] + + brand = self.state.config.metadata.get("brand", "") + if brand in ["xiaomi", "poco"]: + notes.append( + f"- If something goes wrong, you can reinstall MiUI here:\n\n" + ) + + # this should be used as little as possible! + if self.state.config.metadata.get("untested", False): + notes.append( + "- **This device has not been tested with OpenAndroidInstaller yet.** The installation can go wrong. You may have to finish the installation process with command line. If you test, please report the result on GitHub." + ) + + notes.extend( + f"- {note}" for note in self.state.config.metadata.get("notes", []) + ) + return "\n\n".join(notes) + def toggle_additional_image_selection(self): """Toggle the visibility of the additional image selection controls.""" # attach the controls for uploading others partitions, like dtbo, vbmeta & super_empty From b90a5e4fe59acbe99850b6a52774e4dc552ed39f Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 11:06:04 +0200 Subject: [PATCH 14/25] Change the location the skip-buttons to the bottom of the start view. --- openandroidinstaller/views/start_view.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py index 0a9245d5..4833a620 100644 --- a/openandroidinstaller/views/start_view.py +++ b/openandroidinstaller/views/start_view.py @@ -120,7 +120,6 @@ def check_bootloader_unlocked(e): # toggleswitch to allow skipping flashing recovery def check_recovery_already_flashed(e): """Enable skipping flashing recovery if selected.""" - # manage the bootloader unlocking switch if self.bootloader_switch.value == False: self.state.steps = copy.deepcopy(self.state.config.unlock_bootloader) @@ -136,7 +135,7 @@ def check_recovery_already_flashed(e): self.recovery_switch = Switch( label="Custom recovery is already flashed.", on_change=check_recovery_already_flashed, - disabled=False, + disabled=True, inactive_thumb_color=colors.YELLOW, active_color=colors.GREEN, ) @@ -206,8 +205,6 @@ def build(self): If you don't know what this means, you most likely don't need to do anything and you can just continue. """ ), - Row([self.bootloader_switch, self.recovery_switch]), - Divider(), self.device_infobox, Row( [ @@ -223,6 +220,8 @@ def build(self): ], alignment="center", ), + Divider(), + Row([self.bootloader_switch, self.recovery_switch]), ] ) return self.view @@ -278,6 +277,7 @@ def search_devices_clicked(self, e): if device_name: self.continue_button.disabled = False self.bootloader_switch.disabled = False + self.recovery_switch.disabled = False # overwrite the text field with the real name from the config self.device_name.value = ( f"{device_name} (code: {self.state.config.device_code})" From fd29a48fa384000088a25044c58cdf7a23649374 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 11:16:37 +0200 Subject: [PATCH 15/25] Improve typing a bit --- openandroidinstaller/installer_config.py | 3 ++- poetry.lock | 2 +- pyproject.toml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 59419b0c..62e87bdc 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -15,6 +15,7 @@ from pathlib import Path from typing import List, Optional +from typing_extensions import Self import schema import yaml @@ -67,7 +68,7 @@ def __init__( self.twrp_link = metadata.get("twrp-link") @classmethod - def from_file(cls, path): + def from_file(cls, path) -> Self: with open(path, "r", encoding="utf-8") as stream: try: raw_config = yaml.safe_load(stream) diff --git a/poetry.lock b/poetry.lock index 63bc8e14..143175c4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -903,7 +903,7 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [metadata] lock-version = "1.1" python-versions = "<3.11,>=3.9" -content-hash = "f5462dcfd8695f093ffdc7e70d7a90909ab0982f96c3de1564b831223221faeb" +content-hash = "5eb8a128bbc3eb03bb8dd70b45c9e546d7ef88c16b9b562e9c769d1ccc7c0f52" [metadata.files] altgraph = [ diff --git a/pyproject.toml b/pyproject.toml index 61b0fd15..9e20594a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ pytest-mock = "^3.10.0" bandit = "^1.7.4" pytest-subprocess = "^1.5.0" mypy = "^1.0.0" +typing-extensions = "^4.7.1" [tool.poetry.dev-dependencies] From 08fa43f6c1edce110cb784f61ee4c7eccc43a00f Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Wed, 16 Aug 2023 17:26:09 +0200 Subject: [PATCH 16/25] Add an info box with explainations for additional images; show the scrollbar by default if there is scrolling necessary --- openandroidinstaller/views/base.py | 2 +- openandroidinstaller/views/select_view.py | 79 +++++++++++++++++++++-- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/openandroidinstaller/views/base.py b/openandroidinstaller/views/base.py index f32b9d31..e40dd31b 100644 --- a/openandroidinstaller/views/base.py +++ b/openandroidinstaller/views/base.py @@ -34,7 +34,7 @@ def __init__(self, state: AppState, image: str = "placeholder.png"): # right part of the display, add content here. self.right_view_header = Column(width=self.column_width, height=120, spacing=30) self.right_view = Column( - alignment="center", width=self.column_width, height=650, scroll="auto" + alignment="center", width=self.column_width, height=650, scroll="adaptive" ) # left part of the display: used for displaying the images self.left_view = Column( diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 43e5800f..23368255 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -154,8 +154,8 @@ def build(self): ] ) - # create help/info button to show the help dialog - info_button = OutlinedButton( + # create help/info button to show the help dialog for the image and recovery selection + explain_images_button = OutlinedButton( "What is this?", on_click=self.open_explain_images_dlg, expand=True, @@ -168,7 +168,7 @@ def build(self): self.right_view_header.controls.append( get_title( "Now pick an OS image and a recovery file:", - info_button=info_button, + info_button=explain_images_button, step_indicator_img="steps-header-select.png", ) ) @@ -289,7 +289,10 @@ def build(self): return self.view def get_notes(self) -> str: - """Prepare and get notes for the specific device from config.""" + """Prepare and get notes for the specific device from config. + + These notes should be displayed to the user. + """ notes = [] brand = self.state.config.metadata.get("brand", "") @@ -311,12 +314,67 @@ def get_notes(self) -> str: def toggle_additional_image_selection(self): """Toggle the visibility of the additional image selection controls.""" + # dialogue box to explain additional required images + self.dlg_explain_additional_images = AlertDialog( + modal=True, + title=Text("Why do I need additional images and where do I get them?"), + content=Markdown( + f"""## About additional images +Some devices require additional images to be flashed before the recovery and OS image can be flashed. +Not all images explained below are required for all devices. The installer will tell you which images are required for your device. + +### dtbo.img +The `dtbo.img` is a partition image that contains the device tree overlay. + +### vbmeta.img +The `vbmeta.img` is a partition image that contains the verified boot metadata. +This is required to prevent issues with the verified boot process. + +### super_empty.img +The `super_empty.img` is used to wipe the super partition. This is required to +prevent issues with the super partition when flashing a new ROM. + +### vendor_boot.img +The `vendor_boot.img` is a partition image that contains the vendor boot image. + +## Where do I get these images? +You can download the required images for your device from the [LineageOS downloads page](https://download.lineageos.org/devices/{self.state.config.device_code}/builds). +If this download page does not contain the required images, you can try to find them on the [XDA developers forum](https://forum.xda-developers.com/). + """, + auto_follow_links=True, + ), + actions=[ + TextButton( + "Close", on_click=self.close_close_explain_additional_images_dlg + ), + ], + actions_alignment="end", + shape=CountinuosRectangleBorder(radius=0), + ) + + # create help/info button to show the help dialog for the image and recovery selection + explain_additional_images_button = OutlinedButton( + "Why do I need this and where do I get it?", + on_click=self.open_explain_additional_images_dlg, + expand=True, + icon=icons.HELP_OUTLINE_OUTLINED, + icon_color=colors.DEEP_ORANGE_500, + tooltip="Get more details on additional images and download links.", + ) + # attach the controls for uploading others partitions, like dtbo, vbmeta & super_empty additional_image_selection = [] if self.state.config.metadata["additional_steps"]: additional_image_selection.extend( [ - Text("Select required additional images:", style="titleSmall"), + Row( + [ + Text( + "Select required additional images:", style="titleSmall" + ), + explain_additional_images_button, + ] + ), Markdown( """ Your selected device and ROM requires flashing of additional partitions. Please select the required images below. @@ -403,6 +461,17 @@ def close_close_explain_images_dlg(self, e): self.dlg_explain_images.open = False self.page.update() + def open_explain_additional_images_dlg(self, e): + """Open the dialog to explain additional images.""" + self.page.dialog = self.dlg_explain_additional_images + self.dlg_explain_additional_images.open = True + self.page.update() + + def close_close_explain_additional_images_dlg(self, e): + """Close the dialog to explain additional images.""" + self.dlg_explain_additional_images.open = False + self.page.update() + def pick_image_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 From 3789f8228fb683f034583ff4c9e3f942094c63b1 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 22 Aug 2023 12:09:52 +0200 Subject: [PATCH 17/25] Fix vbmeta selection bug and update recovery checks on device codes --- .../assets/configs/Mi439.yaml | 1 + openandroidinstaller/utils.py | 25 ++++++++++++++++--- openandroidinstaller/views/select_view.py | 7 +++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/openandroidinstaller/assets/configs/Mi439.yaml b/openandroidinstaller/assets/configs/Mi439.yaml index 2ab5be21..a5362a32 100644 --- a/openandroidinstaller/assets/configs/Mi439.yaml +++ b/openandroidinstaller/assets/configs/Mi439.yaml @@ -10,6 +10,7 @@ metadata: - super_empty supported_device_codes: - Mi439 + - mi439 - pine - olive - olivelite diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index 42f2dccb..a8511611 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -41,7 +41,15 @@ def get_download_link(devicecode: str) -> Optional[str]: def image_works_with_device(supported_device_codes: List[str], image_path: str) -> bool: - """Determine if an image works for the given device.""" + """Determine if an image works for the given device. + + Args: + supported_device_codes: List of supported device codes from the config file. + image_path: Path to the image file. + + Returns: + True if the image works with the device, False otherwise. + """ with zipfile.ZipFile(image_path) as image_zip: with image_zip.open( "META-INF/com/android/metadata", mode="r" @@ -77,13 +85,24 @@ def image_sdk_level(image_path: str) -> int: return 0 -def recovery_works_with_device(device_code: str, recovery_path: str) -> bool: +def recovery_works_with_device( + supported_device_codes: List[str], recovery_path: str +) -> bool: """Determine if a recovery works for the given device. BEWARE: THE RECOVERY PART IS STILL VERY BASIC! + + Args: + supported_device_codes: List of supported device codes from the config file. + recovery_path: Path to the recovery file. + + Returns: + True if the recovery works with the device, False otherwise. """ recovery_file_name = recovery_path.split("/")[-1] - if (device_code in recovery_file_name) and ("twrp" in recovery_file_name): + if any(code in recovery_file_name for code in supported_device_codes) and ( + "twrp" in recovery_file_name + ): logger.success("Device supported by the selected recovery.") return True else: diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 23368255..6a03b976 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -296,7 +296,11 @@ def get_notes(self) -> str: notes = [] brand = self.state.config.metadata.get("brand", "") - if brand in ["xiaomi", "poco"]: + if brand in "xiaomi": + notes.append( + "- If something goes wrong, you can reinstall MiUI here:\n\n" + ) + elif brand in "poco": notes.append( f"- If something goes wrong, you can reinstall MiUI here:\n\n" ) @@ -552,6 +556,7 @@ def pick_vbmeta_result(self, e: FilePickerResultEvent): # 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.selected_vbmeta.value = True self.state.vbmeta_path = e.files[0].path logger.info(f"Selected vbmeta from {self.state.vbmeta_path}") else: From b0e2a0b8d16379008a80236bf38d9d5da1049578 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 22 Aug 2023 17:50:33 +0200 Subject: [PATCH 18/25] Fix bugs on vbmeta flashing and checking recovery image --- openandroidinstaller/views/select_view.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 6a03b976..b3d78621 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -555,12 +555,13 @@ def pick_vbmeta_result(self, e: FilePickerResultEvent): 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.selected_vbmeta.fill_color = colors.GREEN self.selected_vbmeta.value = True 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 + self.selected_vbmeta.fill_color = colors.RED + self.selected_vbmeta.value = False else: logger.info("No image selected.") # update @@ -589,14 +590,14 @@ def enable_button_if_ready(self, e): if (".zip" in self.selected_image.value) and ( ".img" in self.selected_recovery.value ): - device_code = self.state.config.device_code if not ( image_works_with_device( supported_device_codes=self.state.config.supported_device_codes, image_path=self.state.image_path, ) and recovery_works_with_device( - device_code=device_code, recovery_path=self.state.recovery_path + supported_device_codes=self.state.config.supported_device_codes, + recovery_path=self.state.recovery_path ) ): # if image and recovery work for device allow to move on, otherwise display message From ff5c2a1160b8c23a9ed2acc4b8050b782f74ecbd Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 29 Aug 2023 10:22:37 +0200 Subject: [PATCH 19/25] Update tests --- openandroidinstaller/views/select_view.py | 2 +- tests/test_progress_bar.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index b3d78621..006b44a9 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -597,7 +597,7 @@ def enable_button_if_ready(self, e): ) and recovery_works_with_device( supported_device_codes=self.state.config.supported_device_codes, - recovery_path=self.state.recovery_path + recovery_path=self.state.recovery_path, ) ): # if image and recovery work for device allow to move on, otherwise display message diff --git a/tests/test_progress_bar.py b/tests/test_progress_bar.py index 6d79afe5..31700450 100644 --- a/tests/test_progress_bar.py +++ b/tests/test_progress_bar.py @@ -39,7 +39,7 @@ def test_update_progress_bar(): assert progress_indicator.progress_bar # test if percentages are parsed correctly and update is performed - for percentage in range(0, 47): + for percentage in range(1, 47): line = f"serving: '/home/tobias/Repositories/openandroidinstaller/images/google-pixel3a/lineage-19.1-20221004-nightly-sargo-signed.zip' (~{percentage}%)\n" progress_indicator.display_progress_bar(line) assert progress_indicator.progress_bar.value == percentage / 100 @@ -47,3 +47,7 @@ def test_update_progress_bar(): # test if the finishing print is detected and updated correctly. progress_indicator.display_progress_bar(line="Total xfer: 1.00x\n") assert progress_indicator.progress_bar.value == 0.99 + + # test if the final set_progress_bar is working correctly + progress_indicator.set_progress_bar(100) + assert progress_indicator.progress_bar.value == 1.0 From 0b53accaf205027bf530aa0b5a2d8c3db1c76b2c Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 29 Aug 2023 11:15:39 +0200 Subject: [PATCH 20/25] ADd workflows to not flash the recovery and still work --- openandroidinstaller/app_state.py | 52 ++++++++++- openandroidinstaller/tooling.py | 9 ++ openandroidinstaller/views/select_view.py | 102 ++++++++++++++-------- openandroidinstaller/views/start_view.py | 23 +---- openandroidinstaller/views/step_view.py | 2 + 5 files changed, 131 insertions(+), 57 deletions(-) diff --git a/openandroidinstaller/app_state.py b/openandroidinstaller/app_state.py index 3fa621a3..3af77e1e 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -16,8 +16,9 @@ import copy from pathlib import Path from typing import List, Optional +from loguru import logger -from installer_config import _load_config +from installer_config import _load_config, Step class AppState: @@ -37,6 +38,10 @@ def __init__( self.test = test self.test_config = test_config + # store state + self.unlock_bootloader = True + self.flash_recovery = True + # placeholders self.advanced = False self.install_addons = False @@ -72,3 +77,48 @@ def load_config(self, device_code: str): self.steps = copy.deepcopy(self.config.unlock_bootloader) + copy.deepcopy( self.config.boot_recovery ) + + def toggle_flash_unlock_bootloader(self): + """Toggle flashing of unlock bootloader.""" + self.unlock_bootloader = not self.unlock_bootloader + if self.unlock_bootloader: + logger.info("Enabled unlocking the bootloader again.") + self.steps = copy.deepcopy(self.config.unlock_bootloader) + else: + logger.info("Skipping bootloader unlocking.") + self.steps = [] + # if the recovery is already flashed, skip flashing it again + if self.flash_recovery: + self.steps += copy.deepcopy(self.config.boot_recovery) + else: + self.steps = [ + Step( + title="Boot custom recovery", + type="confirm_button", + content="If you already flashed TWRP, boot into it by pressing 'Confirm and run'. Otherwise restart the process. Once your phone screen looks like the picture on the left, continue.", + command="adb_reboot_recovery", + img="twrp-start.jpeg", + ) + ] + + def toggle_flash_recovery(self): + """Toggle flashing of recovery.""" + self.flash_recovery = not self.flash_recovery + if self.unlock_bootloader: + self.steps = copy.deepcopy(self.config.unlock_bootloader) + else: + self.steps = [] + if self.flash_recovery: + logger.info("Enabled flashing recovery again.") + self.steps += copy.deepcopy(self.config.boot_recovery) + else: + logger.info("Skipping flashing recovery.") + self.steps = [ + Step( + title="Boot custom recovery", + type="confirm_button", + content="If you already flashed TWRP, boot into it by pressing 'Confirm and run'. Otherwise restart the process. Once your phone screen looks like the picture on the left, continue.", + command="adb_reboot_recovery", + img="twrp-start.jpeg", + ) + ] diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index f7c23db2..03dcff5a 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -161,6 +161,15 @@ def adb_wait_for_sideload(bin_path: Path) -> TerminalResponse: yield line +@add_logging("Reboot to recovery with adb") +def adb_reboot_recovery(bin_path: Path) -> TerminalResponse: + """Reboot to recovery with adb.""" + for line in run_command("adb reboot recovery", bin_path): + yield line + for line in adb_wait_for_recovery(bin_path=bin_path): + yield line + + def adb_twrp_copy_partitions(bin_path: Path, config_path: Path) -> TerminalResponse: # 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.") diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 006b44a9..0a1a0736 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -251,33 +251,38 @@ def build(self): ), self.selected_image, Divider(), - Text("Select a TWRP recovery image:", style="titleSmall"), - Markdown( - f""" + ] + ) + if self.state.flash_recovery: + self.right_view.controls.extend( + [ + Text("Select a TWRP recovery image:", style="titleSmall"), + Markdown( + f""" The recovery image should look something like `twrp-3.7.0_12-0-{self.state.config.device_code}.img`. **Note:** This tool **only supports TWRP recoveries**.""", - extension_set="gitHubFlavored", - ), - Row( - [ - FilledButton( - "Pick TWRP recovery file", - icon=icons.UPLOAD_FILE, - on_click=lambda _: self.pick_recovery_dialog.pick_files( - allow_multiple=False, - file_type="custom", - allowed_extensions=["img"], + extension_set="gitHubFlavored", + ), + Row( + [ + FilledButton( + "Pick TWRP 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, ), - expand=True, - ), - ] - ), - self.selected_recovery, - Divider(), - self.additional_image_selection, - ] - ) + ] + ), + self.selected_recovery, + Divider(), + self.additional_image_selection, + ] + ) # attach the bottom buttons self.right_view.controls.extend( @@ -296,11 +301,11 @@ def get_notes(self) -> str: notes = [] brand = self.state.config.metadata.get("brand", "") - if brand in "xiaomi": + if brand == "xiaomi": notes.append( "- If something goes wrong, you can reinstall MiUI here:\n\n" ) - elif brand in "poco": + elif brand == "poco": notes.append( f"- If something goes wrong, you can reinstall MiUI here:\n\n" ) @@ -498,14 +503,15 @@ def pick_image_result(self, e: FilePickerResultEvent): else: self.selected_image.color = colors.RED # if the image works and the sdk level is 33 or higher, show the additional image selection - if ( - self.selected_image.color == colors.GREEN - and image_sdk_level(self.state.image_path) >= 33 - ): - self.toggle_additional_image_selection() - else: - self.additional_image_selection.controls = [] - self.additional_image_selection.update() + if self.state.flash_recovery: + if ( + self.selected_image.color == colors.GREEN + and image_sdk_level(self.state.image_path) >= 33 + ): + self.toggle_additional_image_selection() + else: + self.additional_image_selection.controls = [] + self.additional_image_selection.update() # update self.selected_image.update() @@ -523,9 +529,9 @@ def pick_recovery_result(self, e: FilePickerResultEvent): logger.info("No image selected.") # check if the recovery works with the device and show the filename in different colors accordingly if e.files: - device_code = self.state.config.device_code if recovery_works_with_device( - device_code=device_code, recovery_path=self.state.recovery_path + supported_device_codes=self.state.config.supported_device_codes, + recovery_path=self.state.recovery_path, ): self.selected_recovery.color = colors.GREEN else: @@ -642,5 +648,31 @@ def enable_button_if_ready(self, e): self.info_field.controls = [] self.confirm_button.disabled = False self.right_view.update() + elif (".zip" in self.selected_image.value) and (not self.state.flash_recovery): + if not ( + image_works_with_device( + supported_device_codes=self.state.config.supported_device_codes, + image_path=self.state.image_path, + ) + ): + # if image works for device allow to move on, otherwise display message + logger.error( + "Image doesn't work with the device. Please select a different one." + ) + self.info_field.controls = [ + Text( + "Image doesn't work with the device.", + color=colors.RED, + weight="bold", + ) + ] + self.confirm_button.disabled = True + self.right_view.update() + return + + logger.info("Image works 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 diff --git a/openandroidinstaller/views/start_view.py b/openandroidinstaller/views/start_view.py index 4833a620..8bc9d3fc 100644 --- a/openandroidinstaller/views/start_view.py +++ b/openandroidinstaller/views/start_view.py @@ -13,7 +13,6 @@ # If not, see .""" # Author: Tobias Sterbak -import copy import webbrowser from loguru import logger from typing import Callable @@ -99,15 +98,7 @@ def init_visuals( # 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 = [] - else: - logger.info("Enabled unlocking the bootloader again.") - self.state.steps = copy.deepcopy(self.state.config.unlock_bootloader) - # if the recovery is already flashed, skip flashing it again - if self.recovery_switch.value == False: - self.state.steps += copy.deepcopy(self.state.config.boot_recovery) + self.state.toggle_flash_unlock_bootloader() self.bootloader_switch = Switch( label="Bootloader is already unlocked.", @@ -120,17 +111,7 @@ def check_bootloader_unlocked(e): # toggleswitch to allow skipping flashing recovery def check_recovery_already_flashed(e): """Enable skipping flashing recovery if selected.""" - # manage the bootloader unlocking switch - if self.bootloader_switch.value == False: - self.state.steps = copy.deepcopy(self.state.config.unlock_bootloader) - else: - self.state.steps = [] - - if self.recovery_switch.value: - logger.info("Skipping flashing recovery.") - else: - logger.info("Enabled flashing recovery again.") - self.state.steps += copy.deepcopy(self.state.config.boot_recovery) + self.state.toggle_flash_recovery() self.recovery_switch = Switch( label="Custom recovery is already flashed.", diff --git a/openandroidinstaller/views/step_view.py b/openandroidinstaller/views/step_view.py index 9d431513..3fbe2af3 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -41,6 +41,7 @@ adb_reboot, adb_reboot_bootloader, adb_reboot_download, + adb_reboot_recovery, adb_sideload, adb_twrp_copy_partitions, fastboot_boot_recovery, @@ -216,6 +217,7 @@ def call_to_phone(self, e, command: str): "adb_reboot": adb_reboot, "adb_reboot_bootloader": adb_reboot_bootloader, "adb_reboot_download": adb_reboot_download, + "adb_reboot_recovery": adb_reboot_recovery, "adb_sideload": partial(adb_sideload, target=self.state.image_path), "adb_twrp_copy_partitions": partial( adb_twrp_copy_partitions, config_path=self.state.config_path From 70925970f1b8fd579571ba1a2e7cadad4b611d11 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 29 Aug 2023 12:49:13 +0200 Subject: [PATCH 21/25] Minor fix --- openandroidinstaller/views/select_view.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 0a1a0736..5965d2ae 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -301,14 +301,10 @@ def get_notes(self) -> str: notes = [] brand = self.state.config.metadata.get("brand", "") - if brand == "xiaomi": + if brand in ["xiaomi", "poco"]: notes.append( "- If something goes wrong, you can reinstall MiUI here:\n\n" ) - elif brand == "poco": - notes.append( - f"- If something goes wrong, you can reinstall MiUI here:\n\n" - ) # this should be used as little as possible! if self.state.config.metadata.get("untested", False): From 6c09fbfc09847f49899447fb354fa3938a1de7c0 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 4 Sep 2023 17:56:26 +0200 Subject: [PATCH 22/25] Fix some bugs in the behaviour of the select view with additional images; now allow to skip additional images if you want to --- openandroidinstaller/app_state.py | 2 +- openandroidinstaller/views/select_view.py | 29 +++++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/openandroidinstaller/app_state.py b/openandroidinstaller/app_state.py index 3af77e1e..30f68b08 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -116,7 +116,7 @@ def toggle_flash_recovery(self): self.steps = [ Step( title="Boot custom recovery", - type="confirm_button", + type="call_button", content="If you already flashed TWRP, boot into it by pressing 'Confirm and run'. Otherwise restart the process. Once your phone screen looks like the picture on the left, continue.", command="adb_reboot_recovery", img="twrp-start.jpeg", diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 5965d2ae..da3e08cb 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -122,6 +122,10 @@ def init_visuals( # initialize and manage button state. self.confirm_button = confirm_button(self.on_confirm) self.confirm_button.disabled = True + self.continue_eitherway_button = confirm_button( + self.on_confirm, "Continue without additional images" + ) + self.continue_eitherway_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 @@ -288,7 +292,13 @@ def build(self): self.right_view.controls.extend( [ self.info_field, - Row([self.back_button, self.confirm_button]), + Row( + [ + self.back_button, + self.continue_eitherway_button, + self.confirm_button, + ] + ), ] ) return self.view @@ -614,24 +624,26 @@ def enable_button_if_ready(self, e): ) ] self.confirm_button.disabled = True + self.continue_eitherway_button.disabled = True self.right_view.update() return - # check if the additional images work with the device - if any( - v == False - for v in [ + self.continue_eitherway_button.disabled = False + + # check if the additional images are there + if not all( + [ self.selected_dtbo.value, self.selected_vbmeta.value, self.selected_super_empty.value, ] ): logger.error( - "Some additional images don't match. Please select different ones." + "Some additional images don't match or are missing. Please select different ones." ) self.info_field.controls = [ Text( - "Some additional images don't match. Please select the right ones.", + "Some additional images don't match or are missing. Please select the right ones.", color=colors.RED, weight="bold", ) @@ -663,12 +675,15 @@ def enable_button_if_ready(self, e): ) ] self.confirm_button.disabled = True + self.continue_eitherway_button.disabled = True self.right_view.update() return logger.info("Image works with the device. You can continue.") self.info_field.controls = [] self.confirm_button.disabled = False + self.continue_eitherway_button.disabled = False self.right_view.update() else: self.confirm_button.disabled = True + self.continue_eitherway_button.disabled = True From 83dbd7ae3880454033b99251a1098a5d02a68b6f Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Mon, 4 Sep 2023 18:08:38 +0200 Subject: [PATCH 23/25] Update the message to acompany the install progress bar --- openandroidinstaller/views/install_view.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openandroidinstaller/views/install_view.py b/openandroidinstaller/views/install_view.py index 4b2b7021..6e8c7b4d 100644 --- a/openandroidinstaller/views/install_view.py +++ b/openandroidinstaller/views/install_view.py @@ -54,7 +54,7 @@ def __init__( def build(self): """Create the content of the view.""" # error text - self.error_text = Text("", color=colors.RED) + self.error_text = Text("", color=colors.GREEN) # switch to enable advanced output - here it means show terminal input/output in tool def check_advanced_switch(e): @@ -178,7 +178,8 @@ def run_install(self, e): # disable the call button while the command is running self.install_button.disabled = True self.install_addons_switch.disabled = True - self.error_text.value = "" + self.error_text.value = "Please be patient, it may take a few minutes." + self.error_text.color = colors.GREEN # reset the progress indicators self.progress_indicator.clear() # reset terminal output @@ -208,6 +209,7 @@ def run_install(self, e): self.install_button.disabled = False # also remove the last error text if it happened self.error_text.value = "Installation failed! Try again or make sure everything is setup correctly." + self.error_text.color = colors.RED else: sleep(5) # wait to make sure everything is fine self.progress_indicator.set_progress_bar(100) From 1cfc293819eff34f54645a1563985aecd57e0709 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 5 Sep 2023 13:38:49 +0200 Subject: [PATCH 24/25] Fix bug where the validation still required some additional images even if non are required; hide the additional button to bypass additional images if non are necessary --- openandroidinstaller/installer_config.py | 4 +--- openandroidinstaller/views/select_view.py | 29 +++++++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 62e87bdc..e5be79c7 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -63,7 +63,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.additional_steps = metadata.get("additional_steps", []) self.supported_device_codes = metadata.get("supported_device_codes") self.twrp_link = metadata.get("twrp-link") @@ -136,8 +136,6 @@ 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: diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index da3e08cb..c767d343 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -289,16 +289,19 @@ def build(self): ) # attach the bottom buttons + if self.state.config.additional_steps: + bottom_buttons = [ + self.back_button, + self.continue_eitherway_button, + self.confirm_button, + ] + + else: + bottom_buttons = [self.back_button, self.confirm_button] self.right_view.controls.extend( [ self.info_field, - Row( - [ - self.back_button, - self.continue_eitherway_button, - self.confirm_button, - ] - ), + Row(bottom_buttons), ] ) return self.view @@ -379,7 +382,7 @@ def toggle_additional_image_selection(self): # attach the controls for uploading others partitions, like dtbo, vbmeta & super_empty additional_image_selection = [] - if self.state.config.metadata["additional_steps"]: + if self.state.config.additional_steps: additional_image_selection.extend( [ Row( @@ -398,7 +401,7 @@ def toggle_additional_image_selection(self): ), ] ) - if "dtbo" in self.state.config.metadata["additional_steps"]: + if "dtbo" in self.state.config.additional_steps: self.selected_dtbo.value = False additional_image_selection.extend( [ @@ -419,7 +422,7 @@ def toggle_additional_image_selection(self): ), ] ) - if "vbmeta" in self.state.config.metadata["additional_steps"]: + if "vbmeta" in self.state.config.additional_steps: self.selected_vbmeta.value = False additional_image_selection.extend( [ @@ -440,7 +443,7 @@ def toggle_additional_image_selection(self): ), ] ) - if "super_empty" in self.state.config.metadata["additional_steps"]: + if "super_empty" in self.state.config.additional_steps: self.selected_super_empty.value = False additional_image_selection.extend( [ @@ -631,7 +634,7 @@ def enable_button_if_ready(self, e): self.continue_eitherway_button.disabled = False # check if the additional images are there - if not all( + if self.state.config.additional_steps and not all( [ self.selected_dtbo.value, self.selected_vbmeta.value, @@ -686,4 +689,4 @@ def enable_button_if_ready(self, e): self.right_view.update() else: self.confirm_button.disabled = True - self.continue_eitherway_button.disabled = True + # self.continue_eitherway_button.disabled = True From ed77d85b7fc488248cc03cb880a0371e39c62cf4 Mon Sep 17 00:00:00 2001 From: Tobias Sterbak Date: Tue, 5 Sep 2023 15:00:28 +0200 Subject: [PATCH 25/25] Improve behaviour of the continue buttons and validation --- openandroidinstaller/views/select_view.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index c767d343..1e3e98f0 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -636,9 +636,9 @@ def enable_button_if_ready(self, e): # check if the additional images are there if self.state.config.additional_steps and not all( [ - self.selected_dtbo.value, - self.selected_vbmeta.value, - self.selected_super_empty.value, + self.selected_dtbo.value or "dtbo" not in self.state.config.additional_steps, + self.selected_vbmeta.value or "vbmeta" not in self.state.config.additional_steps, + self.selected_super_empty.value or "super_empty" not in self.state.config.additional_steps, ] ): logger.error( @@ -658,6 +658,7 @@ def enable_button_if_ready(self, e): logger.info("Image and recovery work with the device. You can continue.") self.info_field.controls = [] self.confirm_button.disabled = False + self.continue_eitherway_button.disabled = True self.right_view.update() elif (".zip" in self.selected_image.value) and (not self.state.flash_recovery): if not ( @@ -685,7 +686,7 @@ def enable_button_if_ready(self, e): logger.info("Image works with the device. You can continue.") self.info_field.controls = [] self.confirm_button.disabled = False - self.continue_eitherway_button.disabled = False + self.continue_eitherway_button.disabled = True self.right_view.update() else: self.confirm_button.disabled = True