Skip to content

Commit

Permalink
Dev: flash additional partitions (#246)
Browse files Browse the repository at this point in the history
This PR is based on #220 (by @anon1892) and aims to integrate the code
in the Installer.

- Include the commands `fastboot_flash_recovery` and
`fastboot_reboot_recovery` (thanks to @anon1892)
- Add support for Mi439 (Redmi 7A & co.) (thanks to @anon1892)
- Display device specific notes at the image selection step (thanks to
@anon1892)

Other fixes & additions:
- Display the scroll bar by default if scrolling is necessary
- An info text box for additional images

Todo:
- [x] When the "custom recovery already flashed" button is selected,
user should not have to select recovery file, as in this PR for now.
- [x] User should be able not to select additional partitions, even on
Android 13 (maybe add a button like "custom recovery already flashed".
- [x] When recovery flashing is disabled, `adb reboot recovery` is not
performed. Fix that.
  • Loading branch information
tsterbak authored Sep 26, 2023
2 parents 5e31e38 + aab4785 commit 7e34275
Show file tree
Hide file tree
Showing 15 changed files with 702 additions and 70 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ OnePlus | Nord N200 | [dre](https://wiki.lineageos.org/devices/dre) | | tested
OnePlus | 9 | lemonade | | under development
</details>

<details><summary><b>Xiaomi</b></summary>

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
</details>

And more to come!


Expand All @@ -197,7 +204,6 @@ Please have a look before opening an issue or starting to contribute.
A detailed list can be found [here](https://openandroidinstaller.org/#contribute).



## Tools

- The [Android SDK Platform Tools](https://developer.android.com/studio/releases/platform-tools) (such as adb and fastboot) are [Apache](https://android.googlesource.com/platform/system/adb/+/refs/heads/master/NOTICE)-licensed universal Android utilities
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ A config file consists of two parts. The first part are some metadata about the
Every config file should have `metadata` with the following fields:
- `maintainer`: str; Maintainer and author of the config file.
- `device_name`: str; Name of the device.
- `brand`: [OPTIONAL] str; Name of the brand. Can be used to make brand specific actions.
- `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.
- `additional_steps` : [OPTIONAL] List[str]; A list of additional steps. Can be `dtbo`, `vbmeta`, `vendor_boot` or `super_empty`.
- `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.
Expand All @@ -32,7 +36,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_recovery`, `fastboot_reboot_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.**
Expand Down
55 changes: 54 additions & 1 deletion openandroidinstaller/app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -37,13 +38,20 @@ 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
self.addon_paths = []
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 = []
Expand All @@ -69,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="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",
)
]
68 changes: 68 additions & 0 deletions openandroidinstaller/assets/configs/Mi439.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
metadata:
maintainer: A non (anon)
brand: xiaomi
device_name: Xiaomi Redmi 7A / 8 / 8A / 8A Dual
is_ab_device: false
device_code: Mi439
additional_steps:
- dtbo
- vbmeta
- super_empty
supported_device_codes:
- Mi439
- mi439
- pine
- olive
- olivelite
- olivewood
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)
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
12 changes: 10 additions & 2 deletions openandroidinstaller/installer_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from pathlib import Path
from typing import List, Optional
from typing_extensions import Self

import schema
import yaml
Expand Down Expand Up @@ -62,11 +63,12 @@ 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")

@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)
Expand Down Expand Up @@ -150,7 +152,8 @@ 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_critical|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_critical|fastboot_unlock_with_code|fastboot_get_unlock_data|fastboot_unlock|fastboot_oem_unlock|fastboot_reboot|fastboot_reboot_recovery|heimdall_flash_recovery|fastboot_flash_additional_partitions"""
),
schema.Optional("allow_skip"): bool,
schema.Optional("img"): str,
Expand All @@ -166,6 +169,11 @@ 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("notes"): [str],
schema.Optional("brand"): str,
},
schema.Optional("requirements"): {
schema.Optional("android"): schema.Or(str, int),
Expand Down
90 changes: 89 additions & 1 deletion openandroidinstaller/tooling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -416,7 +425,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
Expand All @@ -440,6 +449,85 @@ 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,
) -> 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.")
Expand Down
Loading

0 comments on commit 7e34275

Please sign in to comment.