Skip to content

Commit

Permalink
Rework the user messages of the image/recovery selection and the vali…
Browse files Browse the repository at this point in the history
…dation process
  • Loading branch information
tsterbak committed Dec 24, 2023
1 parent cc7444c commit d0b6c6f
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 60 deletions.
107 changes: 75 additions & 32 deletions openandroidinstaller/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,35 @@
# Author: Tobias Sterbak

import zipfile
from dataclasses import dataclass
from enum import Enum
from typing import Optional, List

import requests
from loguru import logger


class CompatibilityStatus(Enum):
"""Enum for the compatibility status of a device."""

UNKNOWN = 0
COMPATIBLE = 1
INCOMPATIBLE = 2


@dataclass
class CheckResult:
"""Dataclass for the result of a check.
Attributes:
status: Compatibility status of the device.
message: Message to be displayed to the user.
"""

status: CompatibilityStatus
message: str


def get_download_link(devicecode: str) -> Optional[str]:
"""Check if a lineageOS version for this device exists on download.lineageos.com and return the respective download link."""
url = f"https://download.lineageos.org/api/v2/devices/{devicecode}"
Expand Down Expand Up @@ -61,65 +84,80 @@ def retrieve_image_metadata(image_path: str) -> dict:
].decode("utf-8")
logger.info(f"Metadata retrieved from image {image_path.split('/')[-1]}.")
return metadata_dict
except FileNotFoundError:
except (FileNotFoundError, KeyError):
logger.error(
f"Metadata file {metapath} not found in {image_path.split('/')[-1]}."
)
return dict()


def image_works_with_device(supported_device_codes: List[str], image_path: str) -> bool:
def image_sdk_level(image_path: str) -> int:
"""Determine Android version of the selected image.
Examples:
Android 10: 29
Android 11: 30
Android 12: 31
Android 12.1: 32
Android 13: 33
Args:
image_path: Path to the image file.
Returns:
Android version as integer.
"""
metadata = retrieve_image_metadata(image_path)
try:
sdk_level = metadata["post-sdk-level"]
logger.info(f"Android version of {image_path}: {sdk_level}")
return int(sdk_level)
except (ValueError, TypeError, KeyError) as e:
logger.error(f"Could not determine Android version of {image_path}. Error: {e}")
return -1


def image_works_with_device(
supported_device_codes: List[str], image_path: str
) -> CheckResult:
"""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.
CheckResult object containing the compatibility status and a message.
"""
metadata = retrieve_image_metadata(image_path)
try:
supported_devices = metadata["pre-device"].split(",")
logger.info(f"Image works with the following device(s): {supported_devices}")
if any(code in supported_devices for code in supported_device_codes):
logger.success("Device supported by the selected image.")
return True
return CheckResult(
CompatibilityStatus.COMPATIBLE,
"Device supported by the selected image.",
)
else:
logger.error(f"Image file {image_path.split('/')[-1]} is not supported.")
return False
return CheckResult(
CompatibilityStatus.INCOMPATIBLE,
f"Image file {image_path.split('/')[-1]} is not supported by device code.",
)
except KeyError:
logger.error(
f"Could not determine supported devices for {image_path.split('/')[-1]}."
)
return False


def image_sdk_level(image_path: str) -> int:
"""Determine Android version of the selected image.
Example:
Android 13: 33
Args:
image_path: Path to the image file.
Returns:
Android version as integer.
"""
metadata = retrieve_image_metadata(image_path)
try:
sdk_level = metadata["post-sdk-level"]
logger.info(f"Android version of {image_path}: {sdk_level}")
return int(sdk_level)
except (ValueError, TypeError, KeyError) as e:
logger.error(f"Could not determine Android version of {image_path}. Error: {e}")
return 0
return CheckResult(
CompatibilityStatus.UNKNOWN,
f"Could not determine supported devices for {image_path.split('/')[-1]}. Missing metadata file? You may try to flash the image anyway.",
)


def recovery_works_with_device(
supported_device_codes: List[str], recovery_path: str
) -> bool:
) -> CheckResult:
"""Determine if a recovery works for the given device.
BEWARE: THE RECOVERY PART IS STILL VERY BASIC!
Expand All @@ -129,14 +167,19 @@ def recovery_works_with_device(
recovery_path: Path to the recovery file.
Returns:
True if the recovery works with the device, False otherwise.
CheckResult object containing the compatibility status and a message.
"""
recovery_file_name = recovery_path.split("/")[-1]
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
return CheckResult(
CompatibilityStatus.COMPATIBLE, "Device supported by the selected recovery."
)
else:
logger.error(f"Recovery file {recovery_file_name} is not supported.")
return False
return CheckResult(
CompatibilityStatus.INCOMPATIBLE,
f"Recovery file {recovery_file_name} is not supported by device code in file name.",
)
55 changes: 27 additions & 28 deletions openandroidinstaller/views/select_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
image_works_with_device,
recovery_works_with_device,
image_sdk_level,
CheckResult,
CompatibilityStatus,
)


Expand Down Expand Up @@ -145,6 +147,9 @@ def init_visuals(
icon=icons.ARROW_BACK,
expand=True,
)
# store image and recovery compatibility
self.image_compatibility: CheckResult | None = None
self.recovery_compatibility: CheckResult | None = None

def build(self):
self.clear()
Expand Down Expand Up @@ -533,17 +538,21 @@ def pick_image_result(self, e: FilePickerResultEvent):
logger.info("No image selected.")
# check if the image works with the device and show the filename in different colors accordingly
if e.files:
if image_works_with_device(
self.image_compatibility = image_works_with_device(
supported_device_codes=self.state.config.supported_device_codes,
image_path=self.state.image_path,
):
)
if self.image_compatibility.status == CompatibilityStatus.COMPATIBLE:
self.selected_image.color = colors.GREEN
elif self.image_compatibility.status == CompatibilityStatus.UNKNOWN:
self.selected_image.color = colors.ORANGE
else:
self.selected_image.color = colors.RED
self.selected_image.value += f"\n> {self.image_compatibility.message}"
# if the image works and the sdk level is 33 or higher, show the additional image selection
if self.state.flash_recovery:
if (
self.selected_image.color == colors.GREEN
self.image_compatibility
and image_sdk_level(self.state.image_path) >= 33
):
self.toggle_additional_image_selection()
Expand All @@ -567,13 +576,17 @@ 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:
if recovery_works_with_device(
self.recovery_compatibility = recovery_works_with_device(
supported_device_codes=self.state.config.supported_device_codes,
recovery_path=self.state.recovery_path,
):
)
if self.recovery_compatibility.status == CompatibilityStatus.COMPATIBLE:
self.selected_recovery.color = colors.GREEN
elif self.recovery_compatibility.status == CompatibilityStatus.UNKNOWN:
self.selected_recovery.color = colors.ORANGE
else:
self.selected_recovery.color = colors.RED
self.selected_recovery.value += f"\n> {self.recovery_compatibility.message}"
# update
self.selected_recovery.update()

Expand Down Expand Up @@ -654,23 +667,18 @@ def enable_button_if_ready(self, e):
if (".zip" in self.selected_image.value) and (
".img" in self.selected_recovery.value
):
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(
supported_device_codes=self.state.config.supported_device_codes,
recovery_path=self.state.recovery_path,
)
if (
self.image_compatibility.status == CompatibilityStatus.INCOMPATIBLE
) or (
self.recovery_compatibility.status == CompatibilityStatus.INCOMPATIBLE
):
# if image and recovery work for device allow to move on, otherwise display message
logger.error(
"Image and recovery don't work with the device. Please select different ones."
)
self.info_field.controls = [
Text(
"Image and/or recovery don't work with the device. Make sure you use a TWRP-based recovery.",
"Something is wrong with the selected files.",
color=colors.RED,
weight="bold",
)
Expand All @@ -695,12 +703,10 @@ def enable_button_if_ready(self, e):
or "vendor_boot" not in self.state.config.additional_steps,
]
):
logger.error(
"Some additional images don't match or are missing. Please select different ones."
)
logger.error("Some additional images don't match or are missing.")
self.info_field.controls = [
Text(
"Some additional images don't match or are missing. Please select the right ones.",
"Some additional images don't match or are missing.",
color=colors.RED,
weight="bold",
)
Expand All @@ -715,16 +721,9 @@ def enable_button_if_ready(self, e):
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 (
image_works_with_device(
supported_device_codes=self.state.config.supported_device_codes,
image_path=self.state.image_path,
)
):
if self.image_compatibility.status != CompatibilityStatus.COMPATIBLE:
# 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."
)
logger.error("Image doesn't work with the device.")
self.info_field.controls = [
Text(
"Image doesn't work with the device.",
Expand Down

0 comments on commit d0b6c6f

Please sign in to comment.