diff --git a/openandroidinstaller/openandroidinstaller.py b/openandroidinstaller/openandroidinstaller.py index d7b478e0..615691e3 100644 --- a/openandroidinstaller/openandroidinstaller.py +++ b/openandroidinstaller/openandroidinstaller.py @@ -48,8 +48,10 @@ InstallView, WelcomeView, AddonsView, + InstallAddonsView, ) from tooling import run_command +from installer_config import Step # where to write the logs logger.add("openandroidinstaller.log") @@ -113,9 +115,26 @@ def __init__(self, state: AppState): self.state.final_view = self.final_view # initialize the addon view - self.addon_view = AddonsView(on_confirm=self.confirm, state=self.state) - self.state.addon_view = self.addon_view - + self.select_addon_view = AddonsView(on_confirm=self.confirm, state=self.state) + self.flash_recovery_view = StepView( + step=Step( + title="Flash custom recovery", + type="call_button", + content="Flash a custom recovery (temporarily) by pressing 'Confirm and run'. Once your phone screen looks like the picture on the left, continue.", + command="fastboot_flash_recovery", + img="twrp-start.jpeg", + ), + state=self.state, + on_confirm=self.confirm, + ) + self.install_addons_view = InstallAddonsView( + on_confirm=self.confirm, state=self.state + ) + self.state.addon_views = [ + self.install_addons_view, + self.flash_recovery_view, + self.select_addon_view, + ] def build(self): self.view.controls.append(self.default_views.pop()) diff --git a/openandroidinstaller/tooling.py b/openandroidinstaller/tooling.py index aba01fea..54fea04b 100644 --- a/openandroidinstaller/tooling.py +++ b/openandroidinstaller/tooling.py @@ -150,7 +150,9 @@ def adb_twrp_copy_partitions(bin_path: Path, config_path: Path): return True -def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path, install_addons=True) -> bool: +def adb_twrp_wipe_and_install( + bin_path: Path, target: str, config_path: Path, install_addons=True +) -> bool: """Wipe and format data with twrp, then flash os image with adb. Only works for twrp recovery. @@ -237,6 +239,45 @@ def adb_twrp_wipe_and_install(bin_path: Path, target: str, config_path: Path, in yield True +def adb_twrp_install_addons(bin_path: Path, addons: List[str]) -> bool: + """Flash addons through adb and twrp. + + Only works for twrp recovery. + """ + logger.info("Install addons with twrp.") + sleep(7) + # activate sideload + logger.info("Activate sideload.") + for line in run_command("adb", ["shell", "twrp", "sideload"], bin_path): + yield line + if (type(line) == bool) and not line: + logger.error("Activating sideload failed.") + yield False + return + # now flash os image + sleep(5) + logger.info("Sideload and install addons.") + for addon in addons: + for line in run_command("adb", ["sideload", f"{addon}"], bin_path): + yield line + if (type(line) == bool) and not line: + logger.error(f"Sideloading {addon} failed.") + # TODO: this might sometimes think it failed, but actually it's fine. So skip for now. + # yield False + # return + # finally reboot into os + sleep(7) + logger.info("Reboot into OS.") + for line in run_command("adb", ["reboot"], bin_path): # "shell", "twrp", + yield line + if (type(line) == bool) and not line: + logger.error("Rebooting failed.") + yield False + return + else: + yield True + + def fastboot_unlock_with_code(bin_path: Path, unlock_code: str) -> bool: """Unlock the device with fastboot and code given.""" logger.info(f"Unlock the device with fastboot and code: {unlock_code}.") diff --git a/openandroidinstaller/views/__init__.py b/openandroidinstaller/views/__init__.py index 9c32bf47..93d8d460 100644 --- a/openandroidinstaller/views/__init__.py +++ b/openandroidinstaller/views/__init__.py @@ -6,4 +6,5 @@ from .step_view import StepView # noqa from .install_view import InstallView # noqa from .addon_view import AddonsView # noqa +from .install_addons_view import InstallAddonsView # noqa from .success_view import SuccessView # noqa diff --git a/openandroidinstaller/views/install_addons_view.py b/openandroidinstaller/views/install_addons_view.py new file mode 100644 index 00000000..d46b8e85 --- /dev/null +++ b/openandroidinstaller/views/install_addons_view.py @@ -0,0 +1,180 @@ +"""Contains the install addons view.""" + +# This file is part of OpenAndroidInstaller. +# OpenAndroidInstaller is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. + +# OpenAndroidInstaller is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with OpenAndroidInstaller. +# If not, see .""" +# Author: Tobias Sterbak + +from loguru import logger +from time import sleep +from typing import Callable + +from flet import ( + Column, + ElevatedButton, + Row, + Text, + icons, + Switch, + colors, + Markdown, +) + +from views import BaseView +from app_state import AppState +from tooling import adb_twrp_install_addons +from widgets import ( + confirm_button, + get_title, +) +from views.step_view import TerminalBox, ProgressIndicator + + +class InstallAddonsView(BaseView): + def __init__( + self, + state: AppState, + on_confirm: Callable, + ): + super().__init__(state=state) + self.on_confirm = on_confirm + + def build(self): + """Create the content of the view.""" + # error text + self.error_text = Text("", color=colors.RED) + + # switch to enable advanced output - here it means show terminal input/output in tool + def check_advanced_switch(e): + """Check the box to enable advanced output.""" + if self.advanced_switch.value: + logger.info("Enable advanced output.") + self.state.advanced = True + self.terminal_box.toggle_visibility() + else: + logger.info("Disable advanced output.") + self.state.advanced = False + self.terminal_box.toggle_visibility() + + self.advanced_switch = Switch( + label="Advanced output", + on_change=check_advanced_switch, + disabled=False, + ) + + # text box for terminal output + self.terminal_box = TerminalBox(expand=True) + + # container for progress indicators + self.progress_indicator = ProgressIndicator(expand=True) + + # main controls + self.right_view_header.controls = [ + get_title( + "Install Addons", + step_indicator_img="steps-header-install.png", + ) + ] + self.right_view.controls = [ + Markdown( + """In the next steps, you finally flash the selected Addons. + +Confirm to install. + +This might take a while. At the end your phone will boot into the new OS. +""" + ) + ] + # basic view + logger.info("Starting addon installation.") + self.confirm_button = confirm_button(self.on_confirm) + self.confirm_button.disabled = True + # button to run the installation process + self.install_button = ElevatedButton( + "Confirm and install addons", + on_click=self.run_install_addons, + expand=True, + icon=icons.DIRECTIONS_RUN_OUTLINED, + ) + # build the view + self.right_view.controls.extend( + [ + Row([self.error_text]), + Row([self.progress_indicator]), + Column( + [ + self.advanced_switch, + Row([self.install_button, self.confirm_button]), + ] + ), + Row([self.terminal_box]), + ] + ) + + # if skipping is allowed add a button to the view + if self.state.test: + self.right_view.controls.append( + Row( + [ + Text("Do you want to skip?"), + ElevatedButton( + "Skip", + on_click=self.on_confirm, + icon=icons.NEXT_PLAN_OUTLINED, + expand=True, + ), + ] + ) + ) + return self.view + + def run_install_addons(self, e): + """ + Run the addon installation process trough twrp. + + Some parts of the command are changed by placeholders. + """ + # disable the call button while the command is running + self.install_button.disabled = True + self.install_addons_switch.disabled = True + # reset the progress indicators + self.progress_indicator.clear() + # reset terminal output + if self.state.advanced: + self.terminal_box.clear() + self.right_view.update() + + # run the install script + for line in adb_twrp_install_addons( + addons=self.state.addon_paths, + bin_path=self.state.bin_path, + ): + # write the line to advanced output terminal + self.terminal_box.write_line(line) + # in case the install command is run, we want to update progress ring for now + self.progress_indicator.display_progress_ring() + success = line # the last element of the iterable is a boolean encoding success/failure + + # update the view accordingly + if not success: + # enable call button to retry + 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." + else: + sleep(5) # wait to make sure everything is fine + logger.success("Installation process was successful. Allow to continue.") + # enable the confirm button and disable the call button + self.confirm_button.disabled = False + self.install_button.disabled = True + # reset the progress indicator + self.progress_indicator.clear() + self.view.update() diff --git a/openandroidinstaller/views/install_view.py b/openandroidinstaller/views/install_view.py index cf7d12dc..ca766d19 100644 --- a/openandroidinstaller/views/install_view.py +++ b/openandroidinstaller/views/install_view.py @@ -75,7 +75,7 @@ def check_addons_switch(e): if self.install_addons_switch.value: logger.info("Enable flashing addons.") # add the addons step here. - self.state.default_views.append(self.state.addon_view) + self.state.default_views.extend(self.state.addon_views) self.state.install_addons = True else: logger.info("Disable flashing addons.")