diff --git a/README.md b/README.md index a49b1a4a..66d42b4c 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,12 @@ Linux is currently the best supported platform (tested with Ubuntu 20.04/22.04 L - [LineageOS for microg](https://download.lineage.microg.org) - [BlissRoms](https://blissroms.org) - [PixelExperience](https://download.pixelexperience.org) + - [crDroid](https://crdroid.net/) + - [ArrowOS](https://arrowos.net/) + - [DivestOS](https://divestos.org/) - TWRP Recovery: - [TWRP recovery](https://twrp.me/Devices) + - [OrangeFox](https://wiki.orangefox.tech) - Optional Addons: - There are different packages of *Google Apps* available. - [MindTheGapps](https://wiki.lineageos.org/gapps#downloads) @@ -63,7 +67,7 @@ Linux is currently the best supported platform (tested with Ubuntu 20.04/22.04 L ## Officially supported devices -Currently, the **we support 62 devices** by various vendors and working on adding more soon! +Currently, the **we support 68+ devices** by various vendors and working on adding more soon! Support for these devices is provided as best effort, but things might still go wrong. @@ -169,8 +173,18 @@ OnePlus | Nord N200 | [dre](https://wiki.lineageos.org/devices/dre) | | tested OnePlus | 9 | lemonade | | under development -And more to come! +
Xiaomi + +Vendor | Device Name | CodeName | Models | Status +---|-----------------------------------------------------|------------------------------------------------------------------------------------------|-------|--- +Xiaomi | Redmi Note 7 | [lavender](https://wiki.lineageos.org/devices/lavender) | | tested +Xiaomi | Redmi Note 8 / 8T | [ginkgo / willow](https://wiki.lineageos.org/devices/ginkgo) | | untested +Xiaomi | Redmi Note 10S / 11SE / Poco M5S | [rosemary](https://wiki.lineageos.org/devices/rosemary) / maltose / secret /rosemary_p | | untested +Xiaomi | Redmi 7A / 8 / 8A / 8A Dual | [Mi439](https://wiki.lineageos.org/devices/Mi439) : pine / olive / olivelite / olivewood | | tested +Xiaomi | Redmi 9A / 9C / 9AT / 9i / 9A Sport / 10A / 10A Sport | garden / dandelion / blossom / angelican | | tested +
+And more to come! ## Run OpenAndroidInstaller for development @@ -215,6 +229,9 @@ 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`. +- `supported_recovery`: [OPTIONAL] List[str]; A list of supported recoveries. For the moment, can be twrp and/or orangefox (twrp by default) +- `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. @@ -231,7 +248,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_reboot_recovery`, `fastboot_flash_recovery` `fastboot_unlock_with_code`, `fastboot_flash_additional_partitions`, `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..31d9d226 100644 --- a/openandroidinstaller/app_state.py +++ b/openandroidinstaller/app_state.py @@ -44,6 +44,10 @@ 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 + self.chosen_recovery = None # store views self.default_views: List = [] diff --git a/openandroidinstaller/assets/configs/Mi439.yaml b/openandroidinstaller/assets/configs/Mi439.yaml new file mode 100644 index 00000000..19cc6a46 --- /dev/null +++ b/openandroidinstaller/assets/configs/Mi439.yaml @@ -0,0 +1,73 @@ +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. + allow_skip: true + - 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 + allow_skip: true + 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 diff --git a/openandroidinstaller/assets/configs/garden.yaml b/openandroidinstaller/assets/configs/garden.yaml new file mode 100644 index 00000000..5dec4008 --- /dev/null +++ b/openandroidinstaller/assets/configs/garden.yaml @@ -0,0 +1,59 @@ +metadata: + maintainer: A non (anon) + device_name: Xiaomi Redmi 9A / 9C / 9AT / 9i / 9A Sport / 10A / 10A Sport + is_ab_device: false + device_code: garden + supported_recovery: + - orangefox + - twrp + supported_device_codes: + - dandelion + - garden + - blossom + - angelican + notes: > + - If something goes wrong, you can reinstall MiUI here : https://xiaomifirmwareupdater.com + + - OAI don't support ROM that requires permissiver. + + - 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. + allow_skip: true + - type: link_button_with_confirm + content: > + Please download official Xiaomi unlock tool, and follow instructions. + https://en.miui.com/unlock/download_en.html + You may have to wait 7 days before being able to unlock the bootloader. + Do not attempt to bypass this limitation / use non officials tools, you may brick your phone. + link: https://en.miui.com/unlock/download_en.html + allow_skip: true + - type: confirm_button + content: > + The bootloader is now unlocked. Since the device resets completely, you will need to re-enable USB debugging to continue. + boot_recovery: + - type: confirm_button + content: > + Now you need to boot a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + - type: call_button + content: > + Once the device is fully booted, you need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - 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 diff --git a/openandroidinstaller/assets/configs/ginkgo.yaml b/openandroidinstaller/assets/configs/ginkgo.yaml new file mode 100644 index 00000000..2de8f0be --- /dev/null +++ b/openandroidinstaller/assets/configs/ginkgo.yaml @@ -0,0 +1,57 @@ +metadata: + maintainer: A non (anon) + device_name: Xiaomi Redmi Note 8 / 8T + is_ab_device: false + device_code: ginkgo + supported_recovery: + - orangefox + - twrp + supported_device_codes: + - ginkgo + - willow + notes: > + - If something goes wrong, you can reinstall MiUI here : https://xiaomifirmwareupdater.com + + - You should install Android 10 or newer ROM. + + - 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: + android: 10 (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. + allow_skip: true + - type: link_button_with_confirm + content: > + Please download official Xiaomi unlock tool, and follow instructions. + https://en.miui.com/unlock/download_en.html + You may have to wait 7 days before being able to unlock the bootloader. + Do not attempt to bypass this limitation / use non officials tools, you may brick your phone. + link: https://en.miui.com/unlock/download_en.html + allow_skip: true + - type: confirm_button + content: > + The bootloader is now unlocked. Since the device resets completely, you will need to re-enable USB debugging to continue. + boot_recovery: + - type: confirm_button + content: > + Now you need to boot a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + - type: call_button + content: > + Once the device is fully booted, you need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - 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 diff --git a/openandroidinstaller/assets/configs/lavender.yaml b/openandroidinstaller/assets/configs/lavender.yaml new file mode 100644 index 00000000..02b777ca --- /dev/null +++ b/openandroidinstaller/assets/configs/lavender.yaml @@ -0,0 +1,56 @@ +metadata: + maintainer: A non (anon) + device_name: Xiaomi Redmi Note 7 + is_ab_device: false + device_code: lavender + supported_recovery: + - orangefox + - twrp + supported_device_codes: + - lavender + notes: > + - If something goes wrong, you can reinstall MiUI here : https://xiaomifirmwareupdater.com/miui/lavender/ + + - You should install Android 10 or newer ROM. + + - 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: + android: 10 (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. + allow_skip: true + - type: link_button_with_confirm + content: > + Please download official Xiaomi unlock tool, and follow instructions. + https://en.miui.com/unlock/download_en.html + You may have to wait 7 days before being able to unlock the bootloader. + Do not attempt to bypass this limitation / use non officials tools, you may brick your phone. + link: https://en.miui.com/unlock/download_en.html + allow_skip: true + - type: confirm_button + content: > + The bootloader is now unlocked. Since the device resets completely, you will need to re-enable USB debugging to continue. + boot_recovery: + - type: confirm_button + content: > + Now you need to boot a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + - type: call_button + content: > + Once the device is fully booted, you need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - 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 diff --git a/openandroidinstaller/assets/configs/rosemary.yaml b/openandroidinstaller/assets/configs/rosemary.yaml new file mode 100644 index 00000000..ff2a23c6 --- /dev/null +++ b/openandroidinstaller/assets/configs/rosemary.yaml @@ -0,0 +1,51 @@ +metadata: + maintainer: A non (anon) + device_name: Xiaomi Redmi Note 10S / 11SE / Poco M5S + is_ab_device: false + device_code: rosemary + supported_recovery: + - orangefox + - twrp + supported_device_codes: + - rosemary + - secret + - maltose + - rosemary_p + notes: > + - If something goes wrong, you can reinstall MiUI here : https://xiaomifirmwareupdater.com + + - Please respect the requested by the ROM developers version for MIUI and Android + + - 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) +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. + allow_skip: true + - type: link_button_with_confirm + content: > + Please download official Xiaomi unlock tool, and follow instructions. + https://en.miui.com/unlock/download_en.html + You may have to wait 7 days before being able to unlock the bootloader. + Do not attempt to bypass this limitation / use non officials tools, you may brick your phone. + link: https://en.miui.com/unlock/download_en.html + allow_skip: true + - type: confirm_button + content: > + The bootloader is now unlocked. Since the device resets completely, you will need to re-enable USB debugging to continue. + boot_recovery: + - type: confirm_button + content: > + Now you need to boot a custom recovery system on the phone. A recovery is a small subsystem on your phone, that manages updating, + adapting and repairing of the operating system. + - type: call_button + content: > + Once the device is fully booted, you need to reboot into the bootloader again by pressing 'Confirm and run' here. Then continue. + command: adb_reboot_bootloader + - type: call_button + content: > + Install the recovery you chosen before by pressing 'Confirm and run'. Once it's done continue. + UNTESTED : Need to hold Vol+ or no ? + command: fastboot_flash_boot diff --git a/openandroidinstaller/assets/imgs/ofox.png b/openandroidinstaller/assets/imgs/ofox.png new file mode 100644 index 00000000..2d8197a0 Binary files /dev/null and b/openandroidinstaller/assets/imgs/ofox.png differ diff --git a/openandroidinstaller/installer_config.py b/openandroidinstaller/installer_config.py index 2a857d83..795dc52c 100644 --- a/openandroidinstaller/installer_config.py +++ b/openandroidinstaller/installer_config.py @@ -62,6 +62,8 @@ def __init__( self.requirements = requirements self.device_code = metadata.get("device_code") self.is_ab = metadata.get("is_ab_device", False) + self.supported_recovery = metadata.get("supported_recovery") + self.additional_steps = metadata.get("additional_steps") self.supported_device_codes = metadata.get("supported_device_codes") self.twrp_link = metadata.get("twrp-link") @@ -124,6 +126,8 @@ def _load_config(device_code: str, config_path: Path) -> Optional[InstallerConfi if custom_path: config = InstallerConfig.from_file(custom_path) logger.info(f"Loaded custom device config from {custom_path}.") + if 'supported_recovery' not in config.metadata: + config.metadata.update({"supported_recovery": "['twrp']"}) logger.info(f"Config metadata: {config.metadata}.") return config else: @@ -134,6 +138,10 @@ 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 'supported_recovery' not in config.metadata: + config.metadata.update({"supported_recovery": "['twrp']"}) + if 'additional_steps' not in config.metadata: + config.metadata.update({"additional_steps": "[]"}) logger.info(f"Config metadata: {config.metadata}.") return config else: @@ -150,7 +158,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_flash_recovery|fastboot_unlock_with_code|fastboot_get_unlock_data|fastboot_unlock|fastboot_oem_unlock|fastboot_reboot|heimdall_flash_recovery|fastboot_reboot_recovery|fastboot_flash_additional_partitions" ), schema.Optional("allow_skip"): bool, schema.Optional("img"): str, @@ -166,6 +174,9 @@ def validate_config(config: str) -> bool: "device_code": str, "supported_device_codes": [str], schema.Optional("twrp-link"): str, + schema.Optional("supported_recovery"): [str], + schema.Optional("additional_steps"): [str], + schema.Optional("notes"): str, }, schema.Optional("requirements"): { schema.Optional("android"): schema.Or(str, int), diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index d3a551be..96b5aa8e 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -139,6 +139,11 @@ def activate_sideload(bin_path: Path) -> TerminalResponse: for line in adb_wait_for_sideload(bin_path=bin_path): yield line +@add_logging("Activate sideloading in OranfeFox.", return_if_fail=True) +def activate_sideload_ofox(bin_path: Path) -> TerminalResponse: + """Activate sideload with adb shell in OrangeFox.""" + for line in run_command("adb shell twrp sideload help", bin_path): # Why help ? Don't know, but it works + yield line @add_logging("Wait for device") def adb_wait_for_device(bin_path: Path) -> TerminalResponse: @@ -216,12 +221,13 @@ def adb_twrp_wipe_and_install( target: str, config_path: Path, is_ab: bool, + chosen_recovery: str, install_addons=True, recovery: Optional[str] = None, ) -> TerminalResponse: """Wipe and format data with twrp, then flash os image with adb. - Only works for twrp recovery. + Only works for twrp and orangefox recovery. """ logger.info("Wipe and format data with twrp, then install os image.") for line in adb_wait_for_recovery(bin_path): @@ -239,15 +245,29 @@ def adb_twrp_wipe_and_install( sleep(1) # activate sideload - logger.info("Wiping is done, now activate sideload.") - for line in activate_sideload(bin_path=bin_path): - yield line + if chosen_recovery == 'twrp': + logger.info("Wiping is done, now activate sideload.") + for line in activate_sideload(bin_path=bin_path): + yield line + else: + logger.info("Wiping is done, now activate sideload (ofox)") + for line in activate_sideload_ofox(bin_path=bin_path): + yield line + sleep(5) + # now flash os image logger.info("Sideload and install os image.") for line in adb_sideload(bin_path=bin_path, target=target): yield line + # wipe some cache partitions + if chosen_recovery == 'orangefox': + logger.info("Waiting for OrangeFox to restart...") + for line in adb_wait_for_recovery(bin_path): + yield line sleep(7) + + logger.info("Wiping cache and dalvik...") for partition in ["dalvik", "cache"]: for line in run_command(f"adb shell twrp wipe {partition}", bin_path): yield line @@ -497,3 +517,71 @@ def search_device(platform: str, bin_path: Path) -> Optional[str]: except CalledProcessError: logger.error("Failed to detect a device.") return None + +@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("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 + +@add_logging("Rebooting device to recovery.") +def fastboot_reboot_recovery(bin_path: Path) -> TerminalResponse: + """ + Reboot to recovery with fastboot + + WARNING : On some devices, users should perform a key combo + """ + for line in run_command("fastboot reboot recovery", bin_path): + yield line \ No newline at end of file diff --git a/openandroidinstaller/utils.py b/openandroidinstaller/utils.py index 0c17baf5..2d42928e 100644 --- a/openandroidinstaller/utils.py +++ b/openandroidinstaller/utils.py @@ -60,15 +60,37 @@ def image_works_with_device(supported_device_codes: List[str], image_path: str) return False -def recovery_works_with_device(device_code: str, recovery_path: str) -> bool: +def recovery_works_with_device(supported_device_codes: [str], supported_recovery: [str], recovery_path: str) -> bool: """Determine if a recovery works for the given device. BEWARE: THE RECOVERY PART IS STILL VERY BASIC! """ recovery_file_name = recovery_path.split("/")[-1] - if (device_code in recovery_file_name) and ("twrp" in recovery_file_name): - logger.success("Device supported by the selected recovery.") - return True - else: - logger.error(f"Recovery file {recovery_file_name} is not supported.") + if recovery_file_name[-4:] != ".img": + logger.error(f"The file {recovery_file_name} is not a recovery file") return False + for codename in supported_device_codes: + if (codename in recovery_file_name) and ("twrp" in recovery_file_name or "TWRP" in recovery_file_name)\ + and ("twrp" in supported_recovery or not supported_recovery): + logger.success("Selected recovery supported for this device.") + return True + elif recovery_file_name == "recovery.img" and ("orangefox" in supported_recovery): + logger.error("Cannot check recovery. Supposing it is OrangeFox.") + return True + logger.error(f"Recovery file {recovery_file_name} is not supported.") + return False + +def which_recovery(recovery_path: str) -> str: + """Determine which recovery was selected + This does not replace recovery_works_with_device. + + BEWARE: THE RECOVERY PART IS STILL VERY BASIC! + """ + recovery_file_name = recovery_path.split("/")[-1] + if "twrp" in recovery_file_name or "TWRP" in recovery_file_name: + return "twrp" + if recovery_file_name == "recovery.img": + return "orangefox" + logger.error("Unable to determine which recovery was selected !") + return None + diff --git a/openandroidinstaller/views/install_view.py b/openandroidinstaller/views/install_view.py index de6c3858..04210e74 100644 --- a/openandroidinstaller/views/install_view.py +++ b/openandroidinstaller/views/install_view.py @@ -171,7 +171,7 @@ def check_addons_switch(e): def run_install(self, e): """ - Run the installation process through twrp. + Run the installation process through recovery. Some parts of the command are changed by placeholders. """ @@ -193,6 +193,7 @@ def run_install(self, e): bin_path=self.state.bin_path, install_addons=self.state.install_addons, is_ab=self.state.config.is_ab, + chosen_recovery=self.state.chosen_recovery, recovery=self.state.recovery_path, ): # write the line to advanced output terminal diff --git a/openandroidinstaller/views/select_view.py b/openandroidinstaller/views/select_view.py index 442fa98b..65bac82b 100644 --- a/openandroidinstaller/views/select_view.py +++ b/openandroidinstaller/views/select_view.py @@ -40,7 +40,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, which_recovery class SelectFilesView(BaseView): @@ -83,7 +83,7 @@ def init_visuals( This custom software can include smaller modifications like rooting your device or even replacing the firmware of the device with a completely custom ROM. -OpenAndroidInstaller works with the [TWRP recovery project](https://twrp.me/about).""", +OpenAndroidInstaller works with the [TWRP recovery project](https://twrp.me/about) or [OrangeFox recovery](https://wiki.orangefox.tech/en/home), depending of your device.""", ), actions=[ TextButton("Close", on_click=self.close_close_explain_images_dlg), @@ -91,18 +91,27 @@ def init_visuals( actions_alignment="end", shape=CountinuosRectangleBorder(radius=0), ) - # 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 +131,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( @@ -152,7 +164,7 @@ def build(self): Column( [ Text( - "You can bring your own image and recovery or you download the officially supported image file for your device here:" + "You can bring your own image and recovery or you download the officially supported LineageOS image file for your device here:" ), Row( [ @@ -179,6 +191,27 @@ def build(self): ) ) # attach the controls for uploading image and recovery + recovery, recoveryFile = "", "" + if "twrp" in self.state.config.supported_recovery: + recovery = "TWRP" + recoveryFile = f"`twrp-3.7.0_12-0-{self.state.config.device_code}.img`" + if "orangefox" in self.state.config.supported_recovery: + if recovery != "": + recovery += " or " + recoveryFile += " or " + recovery += "OrangeFox" + recoveryFile += "`recovery.img`" + + 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']}""" + ) + ] + ) + self.right_view.controls.extend( [ Text("Select an OS image:", style="titleSmall"), @@ -202,18 +235,18 @@ def build(self): ), self.selected_image, Divider(), - Text("Select a TWRP recovery image:", style="titleSmall"), + Text(f"Select a {recovery} 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`. +The recovery image should look something like {recoveryFile}. -**Note:** This tool **only supports TWRP recoveries**.""", +**Note:** This tool **only supports {recovery} recovery** for this phone.""", extension_set="gitHubFlavored", ), Row( [ FilledButton( - "Pick TWRP recovery file", + f"Pick {recovery} recovery file", icon=icons.UPLOAD_FILE, on_click=lambda _: self.pick_recovery_dialog.pick_files( allow_multiple=False, @@ -226,6 +259,85 @@ 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( + f""" +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]), ] @@ -250,6 +362,7 @@ def pick_image_result(self, e: FilePickerResultEvent): self.selected_image.value.split(":")[0] + f": {path}" ) if e.files: + # Why two variables needed? self.image_path = e.files[0].path self.state.image_path = e.files[0].path logger.info(f"Selected image from {self.image_path}") @@ -281,29 +394,84 @@ 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, + supported_recovery=self.state.config.supported_recovery, + recovery_path=self.state.recovery_path ): self.selected_recovery.color = colors.GREEN + self.state.chosen_recovery = which_recovery(self.state.recovery_path) + logger.info(f"Chosen recovery : {self.state.chosen_recovery}") else: self.selected_recovery.color = colors.RED # 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 ( ".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, + supported_recovery=self.state.config.supported_recovery, + recovery_path=self.state.recovery_path ) ): # if image and recovery work for device allow to move on, otherwise display message @@ -312,7 +480,7 @@ def enable_button_if_ready(self, e): ) self.info_field.controls = [ Text( - "Image and/or recovery don't work with the device. Make sure you use a TWRP-based recovery.", + "Image and/or recovery don't work with the device. Make sure you use a supported recovery.", color=colors.RED, weight="bold", ) @@ -320,6 +488,22 @@ 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..9e2957d0 100644 --- a/openandroidinstaller/views/step_view.py +++ b/openandroidinstaller/views/step_view.py @@ -45,12 +45,15 @@ adb_twrp_copy_partitions, fastboot_boot_recovery, fastboot_flash_boot, + fastboot_flash_recovery, fastboot_oem_unlock, fastboot_reboot, fastboot_unlock, fastboot_unlock_with_code, fastboot_get_unlock_data, heimdall_flash_recovery, + fastboot_flash_additional_partitions, + fastboot_reboot_recovery, ) from widgets import ( call_button, @@ -235,6 +238,19 @@ def call_to_phone(self, e, command: str): "heimdall_flash_recovery": partial( heimdall_flash_recovery, 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_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_recovery": fastboot_reboot_recovery, } # run the right command