diff --git a/Main.py b/Main.py index 557c672..28733e8 100644 --- a/Main.py +++ b/Main.py @@ -12,7 +12,8 @@ import time import traceback import webbrowser -from datetime import datetime +import threading +from datetime import datetime, timedelta from urllib.parse import urlparse import darkdetect @@ -76,12 +77,20 @@ def write(self,string): if not self.logfile.closed: self.logfile.write(string) + self.logfile.flush() + + # # noinspection PyMethodMayBeStatic + # def flush(self): + # # noinspection PyStatementEffect + # None - # noinspection PyMethodMayBeStatic def flush(self): - # noinspection PyStatementEffect - None + if not self.logfile.closed: + self.logfile.flush() + def close(self): + if not self.logfile.closed: + self.logfile.close() # ============================================================================ # Class DropDownButton @@ -106,6 +115,196 @@ def OnLinkSelected(self, event, url): print(f"Selected link: {url}") open_device_image_download_link(url) +# ============================================================================ +# Class GoogleImagesBaseMenu +# ============================================================================ +class GoogleImagesBaseMenu(wx.Menu): + def __init__(self, parent): + super(GoogleImagesBaseMenu, self).__init__() + + self.parent = parent + self.load_data() + + def bind_download_event(self, menu, url): + self.parent.Bind(wx.EVT_MENU, lambda event, u=url: self.on_download(u, event), id=menu.GetId()) + + def load_data(self): + json_file_path = os.path.join(get_config_path(), "google_images.json").strip() + if not os.path.exists(json_file_path) or self.is_data_update_required(): + get_google_images() + self.parent.config.google_images_last_checked = int(datetime.now().timestamp()) + try: + with open(json_file_path, 'r', encoding='utf-8') as json_file: + self.data = json.load(json_file) + except FileNotFoundError: + print("google_images.json file not found.") + self.data = {} + + def is_data_update_required(self): + last_checked = self.parent.config.google_images_last_checked + update_frequency = self.parent.config.google_images_update_frequency + # don't check for updates if it is set to -1 + if update_frequency == -1: + return False + if last_checked is None: + return True + current_time = int(datetime.now().timestamp()) + update_threshold = current_time - (update_frequency * 24 * 60 * 60) + return last_checked < update_threshold + + def on_download(self, url, event=None): + def download_completed(): + self.parent.toast("Download Successful", f"File downloaded successfully: {url}") + + filename = os.path.basename(url) + dialog = wx.FileDialog(None, "Save File", defaultFile=filename, wildcard="All files (*.*)|*.*", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + if dialog.ShowModal() == wx.ID_OK: + print(f"Starting background download for: {url} please be patient ...") + destination_path = dialog.GetPath() + download_thread = threading.Thread(target=download_file, args=(url, destination_path), kwargs={"callback": download_completed}) + download_thread.start() + +# ============================================================================ +# Class GoogleImagesMenu +# ============================================================================ +class GoogleImagesMenu(GoogleImagesBaseMenu): + def __init__(self, parent): + super(GoogleImagesMenu, self).__init__(parent) + + try: + self.phones_menu = wx.Menu() + self.watches_menu = wx.Menu() + device = get_phone() + device_hardware = None + device_firmware_date = None + download_available = False + phone_icon = images.phone_green_24.GetBitmap() + watch_icon = images.watch_green_24.GetBitmap() + device_icon = images.star_green_24.GetBitmap() + if hasattr(self.parent, 'firmware_button') and self.parent.firmware_button: + self.parent.firmware_button.SetBitmap(images.open_link_24.GetBitmap()) + + if device: + device_hardware = device.hardware + device_firmware_date = device.firmware_date + + for device_id, device_data in self.data.items(): + device_label = device_data['label'] + device_type = device_data['type'] + device_menu = wx.Menu() + device_download_flag = False + + for download_type in ['ota', 'factory']: + download_menu = wx.Menu() + + for download_entry in reversed(device_data[download_type]): + version = download_entry['version'] + url = download_entry['url'] + sha256 = download_entry['sha256'] + download_date = download_entry['date'] + + menu_label = f"{version} ({device_label})" + menu_id = wx.NewId() + download_menu_item = download_menu.Append(menu_id, menu_label, sha256) + # Set the background color and the icon for the current device. (background color is not working) + if device_id == device_hardware and device_firmware_date and download_date and int(download_date) > int(device_firmware_date): + download_menu_item.SetBackgroundColour((100, 155, 139, 255)) + download_menu_item.SetBitmap(images.download_24.GetBitmap()) + device_download_flag = True + download_available = True + device_icon = images.download_24.GetBitmap() + if device_type == "phone": + phone_icon = images.download_24.GetBitmap() + elif device_type == "watch": + watch_icon = images.download_24.GetBitmap() + + self.bind_download_event(download_menu_item, url) + + download_type_menu_item = device_menu.AppendSubMenu(download_menu, download_type.capitalize()) + if download_type == "ota": + download_type_menu_item.SetBitmap(images.cloud_24.GetBitmap()) + elif download_type == "factory": + download_type_menu_item.SetBitmap(images.factory_24.GetBitmap()) + + if device_download_flag: + download_type_menu_item.SetBitmap(images.download_24.GetBitmap()) + + if device_type == 'phone': + device_menu_item = self.phones_menu.AppendSubMenu(device_menu, f"{device_id} ({device_label})") + # Set the background color and the icon for the current device. (background color is not working) + if device_id == device_hardware: + device_menu_item.SetBitmap(device_icon) + device_menu_item.SetBackgroundColour((100, 155, 139, 255)) + elif device_type == 'watch': + device_menu_item = self.watches_menu.AppendSubMenu(device_menu, f"{device_id} ({device_label})") + # Set the background color and the icon for the current device. (background color is not working) + if device_id == device_hardware: + device_menu_item.SetBitmap(device_icon) + device_menu_item.SetBackgroundColour((100, 155, 139, 255)) + + phone_menu_item = self.AppendSubMenu(self.phones_menu, "Phones") + phone_menu_item.SetBitmap(phone_icon) + watches_menu_item = self.AppendSubMenu(self.watches_menu, "Watches") + watches_menu_item.SetBitmap(watch_icon) + + if download_available: + self.parent.toast("Updates are available", f"There are updates available for your device.\nCheck Google Images menu.") + if hasattr(self.parent, 'firmware_button') and self.parent.firmware_button: + self.parent.firmware_button.SetBitmap(images.open_link_red_24.GetBitmap()) + + except Exception as e: + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while building Google Images Menu.") + traceback.print_exc() + +# ============================================================================ +# Class GoogleImagesPopupMenu +# ============================================================================ +class GoogleImagesPopupMenu(GoogleImagesBaseMenu): + def __init__(self, parent, device=None, date_filter=None): + super(GoogleImagesPopupMenu, self).__init__(parent) + + try: + if device in self.data: + device_data = self.data[device] + + submenu_ota = wx.Menu() + submenu_factory = wx.Menu() + download_flag = False + + for download_entry in reversed(device_data['ota']): + if not date_filter or (download_entry['date'] and int(download_entry['date']) >= int(date_filter)): + version = download_entry['version'] + menu_label = f"{version} (OTA)" + menu_id = wx.NewId() + menu_item = submenu_ota.Append(menu_id, menu_label) + self.parent.Bind(wx.EVT_MENU, lambda event, u=download_entry['url']: self.on_download(u), menu_item) + if int(download_entry['date']) != int(date_filter): + menu_item.SetBitmap(images.download_24.GetBitmap()) + download_flag = True + + for download_entry in reversed(device_data['factory']): + if not date_filter or (download_entry['date'] and int(download_entry['date']) >= int(date_filter)): + version = download_entry['version'] + menu_label = f"{version} (Factory)" + menu_id = wx.NewId() + menu_item = submenu_factory.Append(menu_id, menu_label) + self.parent.Bind(wx.EVT_MENU, lambda event, u=download_entry['url']: self.on_download(u), menu_item) + if int(download_entry['date']) != int(date_filter): + menu_item.SetBitmap(images.download_24.GetBitmap()) + download_flag = True + + ota_menu_item = self.AppendSubMenu(submenu_ota, "OTA") + factory_menu_item = self.AppendSubMenu(submenu_factory, "Factory") + if download_flag: + ota_menu_item.SetBitmap(images.download_24.GetBitmap()) + factory_menu_item.SetBitmap(images.download_24.GetBitmap()) + else: + ota_menu_item.SetBitmap(images.cloud_24.GetBitmap()) + factory_menu_item.SetBitmap(images.factory_24.GetBitmap()) + + except Exception as e: + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while building Google Images Popup Menu.") + traceback.print_exc() # ============================================================================ # Class PixelFlasher @@ -135,8 +334,9 @@ def __init__(self, parent, title): self._build_menu_bar() self._init_ui() - sys.stdout = RedirectText(self.console_ctrl) - sys.stderr = RedirectText(self.console_ctrl) + redirect_text = RedirectText(self.console_ctrl) + sys.stdout = redirect_text + sys.stderr = redirect_text # self.Centre(wx.BOTH) if self.config.pos_x and self.config.pos_y: @@ -834,6 +1034,10 @@ def _build_menu_bar(self): self.xml_view_menu_item = device_menu.Append(wx.ID_ANY, "Dump Screen XML", "Use uiautomator to dump the screen view in xml") self.xml_view_menu_item.SetBitmap(images.xml_24.GetBitmap()) self.Bind(wx.EVT_MENU, self._on_xml_view, self.xml_view_menu_item) + # Cancel OTA Update Menu + self.cancel_ota_menu_item = device_menu.Append(wx.ID_ANY, "Cancel OTA Update", "Cancels and Resets OTA updates by Google (Not PixelFlasher)") + self.cancel_ota_menu_item.SetBitmap(images.cancel_ota_24.GetBitmap()) + self.Bind(wx.EVT_MENU, self._on_cancel_ota, self.cancel_ota_menu_item) # # Verity / Verification Menu # self.verity_menu_item = device_menu.Append(wx.ID_ANY, "Verity / Verification Status", "Check Verity / Verification Status") # self.verity_menu_item.SetBitmap(images.shield_24.GetBitmap()) @@ -1012,46 +1216,71 @@ def _build_menu_bar(self): # Help Menu Items # --------------- # Report an issue - issue_item = help_menu.Append(wx.ID_ANY, 'Report an Issue', 'Report an Issue') - issue_item.SetBitmap(images.bug_24.GetBitmap()) - self.Bind(wx.EVT_MENU, self._on_report_an_issue, issue_item) + self.issue_item = help_menu.Append(wx.ID_ANY, 'Report an Issue', 'Report an Issue') + self.issue_item.SetBitmap(images.bug_24.GetBitmap()) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.issue_item) # # Feature Request - feature_item = help_menu.Append(wx.ID_ANY, 'Feature Request', 'Feature Request') - feature_item.SetBitmap(images.feature_24.GetBitmap()) - self.Bind(wx.EVT_MENU, self._on_feature_request, feature_item) + self.feature_item = help_menu.Append(wx.ID_ANY, 'Feature Request', 'Feature Request') + self.feature_item.SetBitmap(images.feature_24.GetBitmap()) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.feature_item) # # Project Home - project_page_item = help_menu.Append(wx.ID_ANY, 'PixelFlasher Project Page', 'PixelFlasher Project Page') - project_page_item.SetBitmap(images.github_24.GetBitmap()) - self.Bind(wx.EVT_MENU, self._on_project_page, project_page_item) + self.project_page_item = help_menu.Append(wx.ID_ANY, 'PixelFlasher Project Page', 'PixelFlasher Project Page') + self.project_page_item.SetBitmap(images.github_24.GetBitmap()) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.project_page_item) # Community Forum - forum_item = help_menu.Append(wx.ID_ANY, 'PixelFlasher Community (Forum)', 'PixelFlasher Community (Forum)') - forum_item.SetBitmap(images.forum_24.GetBitmap()) - self.Bind(wx.EVT_MENU, self._on_forum, forum_item) + self.forum_item = help_menu.Append(wx.ID_ANY, 'PixelFlasher Community (Forum)', 'PixelFlasher Community (Forum)') + self.forum_item.SetBitmap(images.forum_24.GetBitmap()) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.forum_item) # seperator help_menu.AppendSeparator() # Links Submenu links = wx.Menu() - linksMenuItem1 = links.Append(wx.ID_ANY, "Homeboy76\'s Guide") - linksMenuItem2 = links.Append(wx.ID_ANY, "V0latyle\'s Guide") - linksMenuItem3 = links.Append(wx.ID_ANY, "roirraW\'s Guide") - linksMenuItem4 = links.Append(wx.ID_ANY, "kdrag0n\'s safetynet-fix") - linksMenuItem5 = links.Append(wx.ID_ANY, "Displax\'s safetynet-fix") - linksMenuItem6 = links.Append(wx.ID_ANY, "Get the Google USB Driver") - linksMenuItem7 = links.Append(wx.ID_ANY, "Android Security Update Bulletins") - linksMenuItem1.SetBitmap(images.guide_24.GetBitmap()) - linksMenuItem2.SetBitmap(images.guide_24.GetBitmap()) - linksMenuItem3.SetBitmap(images.guide_24.GetBitmap()) - linksMenuItem4.SetBitmap(images.open_link_24.GetBitmap()) - linksMenuItem5.SetBitmap(images.open_link_24.GetBitmap()) - linksMenuItem6.SetBitmap(images.open_link_24.GetBitmap()) - linksMenuItem7.SetBitmap(images.open_link_24.GetBitmap()) - self.Bind(wx.EVT_MENU, self._on_guide1, linksMenuItem1) - self.Bind(wx.EVT_MENU, self._on_guide2, linksMenuItem2) - self.Bind(wx.EVT_MENU, self._on_guide3, linksMenuItem3) - self.Bind(wx.EVT_MENU, self._on_link1, linksMenuItem4) - self.Bind(wx.EVT_MENU, self._on_link2, linksMenuItem5) - self.Bind(wx.EVT_MENU, self._on_link3, linksMenuItem6) - self.Bind(wx.EVT_MENU, self._on_link4, linksMenuItem7) + self.linksMenuItem1 = links.Append(wx.ID_ANY, "Homeboy76\'s Guide") + self.linksMenuItem2 = links.Append(wx.ID_ANY, "V0latyle\'s Guide") + self.linksMenuItem3 = links.Append(wx.ID_ANY, "roirraW\'s Guide") + links.AppendSeparator() + self.linksMenuItem4 = links.Append(wx.ID_ANY, "osm0sis\'s PlayIntegrityFork") + self.linksMenuItem5 = links.Append(wx.ID_ANY, "chiteroman\'s PlayIntegrityFix") + self.linksMenuItem8 = links.Append(wx.ID_ANY, "TheFreeman193\'s Play Integrity Fix Props Collection") + links.AppendSeparator() + self.linksMenuItem6 = links.Append(wx.ID_ANY, "Get the Google USB Driver") + self.linksMenuItem7 = links.Append(wx.ID_ANY, "Android Security Update Bulletins") + links.AppendSeparator() + self.linksMenuItem9 = links.Append(wx.ID_ANY, "Full OTA Images for Pixel Phones / Tablets") + self.linksMenuItem10 = links.Append(wx.ID_ANY, "Factory Images for Pixel Phones / Tablets") + self.linksMenuItem11 = links.Append(wx.ID_ANY, "Full OTA Images for Pixel Watches") + self.linksMenuItem12 = links.Append(wx.ID_ANY, "Factory Images for Pixel Watches") + links.AppendSeparator() + self.linksMenuItem13 = links.Append(wx.ID_ANY, "Full OTA Images for Pixel Beta 14") + self.linksMenuItem14 = links.Append(wx.ID_ANY, "Factory Images for Pixel Beta 14") + self.linksMenuItem1.SetBitmap(images.guide_24.GetBitmap()) + self.linksMenuItem2.SetBitmap(images.guide_24.GetBitmap()) + self.linksMenuItem3.SetBitmap(images.guide_24.GetBitmap()) + self.linksMenuItem4.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem5.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem6.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem7.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem8.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem9.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem10.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem11.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem12.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem13.SetBitmap(images.open_link_24.GetBitmap()) + self.linksMenuItem14.SetBitmap(images.open_link_24.GetBitmap()) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem1) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem2) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem3) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem4) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem5) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem6) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem7) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem8) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem9) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem10) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem11) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem12) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem13) + self.Bind(wx.EVT_MENU, self._on_link_clicked, self.linksMenuItem14) links_item = help_menu.Append(wx.ID_ANY, 'Links', links) links_item.SetBitmap(images.open_link_24.GetBitmap()) # seperator @@ -1086,6 +1315,10 @@ def _build_menu_bar(self): self.menuBar.Append(file_menu, "&File") # Add the Device menu to the menu bar self.menuBar.Append(device_menu, "&Device") + # Create an instance of GoogleImagesMenu + self.google_images_menu = GoogleImagesMenu(self) + # Append GoogleImagesMenu to the menu bar + self.menuBar.Append(self.google_images_menu, "Google Images") # Add the Toolbar menu to the menu bar self.menuBar.Append(tb_menu, "&Toolbar") # Add the Help menu to the menu bar @@ -1388,147 +1621,47 @@ def _on_resize(self, event): event.Skip(True) # ----------------------------------------------- - # _on_report_an_issue - # ----------------------------------------------- - # Menu methods - def _on_report_an_issue(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://github.com/badabing2005/PixelFlasher/issues/new') - puml(f":Open Link;\nnote right\n=== Report an Issue\n[[https://github.com/badabing2005/PixelFlasher/issues/new]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') - - # ----------------------------------------------- - # _on_feature_request + # _on_link_clicked # ----------------------------------------------- - def _on_feature_request(self, event): + def _on_link_clicked(self, event): try: self._on_spin('start') - webbrowser.open_new('https://github.com/badabing2005/PixelFlasher/issues/new') - puml(f":Open Link;\nnote right\n=== Feature Request\n[[https://github.com/badabing2005/PixelFlasher/issues/new]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') - - # ----------------------------------------------- - # _on_project_page - # ----------------------------------------------- - def _on_project_page(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://github.com/badabing2005/PixelFlasher') - puml(f":Open Link;\nnote right\n=== Github Project Page\n[[https://github.com/badabing2005/PixelFlasher]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') - - # ----------------------------------------------- - # _on_forum - # ----------------------------------------------- - def _on_forum(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://forum.xda-developers.com/t/pixelflasher-gui-tool-that-facilitates-flashing-updating-pixel-phones.4415453/') - puml(f":Open Link;\nnote right\n=== PixelFlasher @XDA\n[[https://forum.xda-developers.com/t/pixelflasher-gui-tool-that-facilitates-flashing-updating-pixel-phones.4415453/]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') - - # ----------------------------------------------- - # _on_guide1 - # ----------------------------------------------- - def _on_guide1(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://xdaforums.com/t/guide-november-6-2023-root-pixel-8-pro-unlock-bootloader-pass-safetynet-both-slots-bootable-more.4638510/#post-89128833/') - puml(f":Open Link;\nnote right\n=== Homeboy76's Guide\n[[https://xdaforums.com/t/guide-november-6-2023-root-pixel-8-pro-unlock-bootloader-pass-safetynet-both-slots-bootable-more.4638510/#post-89128833/]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') - - # ----------------------------------------------- - # _on_guide2 - # ----------------------------------------------- - def _on_guide2(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://forum.xda-developers.com/t/guide-root-pixel-6-oriole-with-magisk.4356233/') - puml(f":Open Link;\nnote right\n=== V0latyle's Guide\n[[https://forum.xda-developers.com/t/guide-root-pixel-6-oriole-with-magisk.4356233/]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') - - # ----------------------------------------------- - # _on_guide3 - # ----------------------------------------------- - def _on_guide3(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://forum.xda-developers.com/t/december-5-2022-tq1a-221205-011-global-012-o2-uk-unlock-bootloader-root-pixel-7-pro-cheetah-safetynet.4502805/') - puml(f":Open Link;\nnote right\n=== roirraW's Guide\n[[https://forum.xda-developers.com/t/december-5-2022-tq1a-221205-011-global-012-o2-uk-unlock-bootloader-root-pixel-7-pro-cheetah-safetynet.4502805/]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') - - # ----------------------------------------------- - # _on_link1 - # ----------------------------------------------- - def _on_link1(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://github.com/kdrag0n/safetynet-fix/releases') - puml(f":Open Link;\nnote right\n=== kdrag0n's Universal Safetynet Fix\n[[https://github.com/kdrag0n/safetynet-fix/releases]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') + clicked_id = event.GetId() + + # A dictionary mapping menu item IDs to tuples containing URL and link description + link_info = { + self.issue_item.GetId(): ('https://github.com/badabing2005/PixelFlasher/issues/new', "Report an Issue"), + self.feature_item.GetId(): ('https://github.com/badabing2005/PixelFlasher/issues/new', "Feature Request"), + self.project_page_item.GetId(): ('https://github.com/badabing2005/PixelFlasher', "PixelFlasher Project Page"), + self.forum_item.GetId(): ('https://forum.xda-developers.com/t/pixelflasher-gui-tool-that-facilitates-flashing-updating-pixel-phones.4415453/', "PixelFlasher Community (Forum)"), + self.linksMenuItem1.GetId(): ('https://xdaforums.com/t/guide-november-6-2023-root-pixel-8-pro-unlock-bootloader-pass-safetynet-both-slots-bootable-more.4638510/#post-89128833/', "Homeboy76's Guide"), + self.linksMenuItem2.GetId(): ('https://forum.xda-developers.com/t/guide-root-pixel-6-oriole-with-magisk.4356233/', "V0latyle's Guide"), + self.linksMenuItem3.GetId(): ('https://forum.xda-developers.com/t/december-5-2022-tq1a-221205-011-global-012-o2-uk-unlock-bootloader-root-pixel-7-pro-cheetah-safetynet.4502805/', "roirraW's Guide"), + self.linksMenuItem4.GetId(): ('https://github.com/osm0sis/PlayIntegrityFork', "osm0sis's PlayIntegrityFork"), + self.linksMenuItem5.GetId(): ('https://github.com/chiteroman/PlayIntegrityFix', "chiteroman's PlayIntegrityFix"), + self.linksMenuItem6.GetId(): ('https://developer.android.com/studio/run/win-usb?authuser=1%2F', "Get the Google USB Driver"), + self.linksMenuItem7.GetId(): ('https://source.android.com/docs/security/bulletin/', "Android Security Update Bulletins"), + self.linksMenuItem8.GetId(): ('https://github.com/TheFreeman193/PIFS', "TheFreeman193's Play Integrity Fix Props Collection"), + self.linksMenuItem9.GetId(): (FULL_OTA_IMAGES_FOR_PIXEL_DEVICES, "Full OTA Images for Pixel Phones, Tablets"), + self.linksMenuItem10.GetId(): (FACTORY_IMAGES_FOR_PIXEL_DEVICES, "Factory Images for Pixel Phones, Tablets"), + self.linksMenuItem11.GetId(): (FULL_OTA_IMAGES_FOR_WATCH_DEVICES, "Full OTA Images for Pixel Watches"), + self.linksMenuItem12.GetId(): (FACTORY_IMAGES_FOR_WATCH_DEVICES, "Factory Images for Pixel Watches"), + self.linksMenuItem13.GetId(): (FULL_OTA_IMAGES_FOR_BETA, "Full OTA Images for Pixel Beta 14"), + self.linksMenuItem14.GetId(): (FACTORY_IMAGES_FOR_BETA, "Factory Images for Pixel Beta 14"), + } - # ----------------------------------------------- - # _on_link2 - # ----------------------------------------------- - def _on_link2(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://github.com/Displax/safetynet-fix/releases') - puml(f":Open Link;\nnote right\n=== Displax's Universal Safetynet Fix\n[[https://github.com/Displax/safetynet-fix/releases]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() - self._on_spin('stop') + if clicked_id in link_info: + url, description = link_info[clicked_id] + webbrowser.open_new(url) + print(f"Open Link {description} {url}") + puml(f":Open Link;\nnote right\n=== {description}\n[[{url}]]\nend note\n", True) + else: + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Unknown menu item clicked") - # ----------------------------------------------- - # _on_link3 (USB Drivers) - # ----------------------------------------------- - def _on_link3(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://developer.android.com/studio/run/win-usb?authuser=1%2F') - puml(f":Open Link;\nnote right\n=== Google USB Driver\n[[https://developer.android.com/studio/run/win-usb?authuser=1%2F]]\nend note\n", True) except Exception as e: print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") traceback.print_exc() - self._on_spin('stop') - # ----------------------------------------------- - # _on_link4 (Security Bulletin) - # ----------------------------------------------- - def _on_link4(self, event): - try: - self._on_spin('start') - webbrowser.open_new('https://source.android.com/docs/security/bulletin/') - puml(f":Open Link;\nnote right\n=== Android security bulletins\n[[https://source.android.com/docs/security/bulletin/]]\nend note\n", True) - except Exception as e: - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while opening a link") - traceback.print_exc() self._on_spin('stop') # ----------------------------------------------- @@ -1636,6 +1769,17 @@ def _on_xml_view(self, event): device.ui_action(f"/data/local/tmp/screen_dump_{timestr}.xml", pathname) self._on_spin('stop') + # ----------------------------------------------- + # _on_cancel_ota + # ----------------------------------------------- + def _on_cancel_ota(self, event): + self._on_spin('start') + timestr = time.strftime('%Y-%m-%d_%H-%M-%S') + print(f"{datetime.now():%Y-%m-%d %H:%M:%S} User Pressed Cancel OTA Update") + device = get_phone() + device.reset_ota_update() + self._on_spin('stop') + # ----------------------------------------------- # Test # ----------------------------------------------- @@ -1936,38 +2080,44 @@ def _update_custom_flash_options(self): # _select_configured_device # ----------------------------------------------- def _select_configured_device(self): - if self.config.device: - count = 0 - for device in get_phones(): - if device.id == self.config.device: - self.device_choice.Select(count) - set_phone_id(device.id) - puml(f":Select Device;\n", True) - self._print_device_details(device) - count += 1 - elif self.device_choice.StringSelection: - device = self.device_choice.StringSelection - # replace multiple spaces with a single space and then split on space - id = ' '.join(device.split()) - id = id.split() - id = id[2] - self.config.device = id - for device in get_phones(): - if device.id == id: - set_phone_id(device.id) - puml(f":Select Device;\n", True) - self._print_device_details(device) - else: - set_phone_id(None) - self.device_label.Label = "ADB Connected Devices" - if self.device_choice.StringSelection == '': - set_phone_id(None) - self.device_label.Label = "ADB Connected Devices" - self.config.device = None - print(f"{datetime.now():%Y-%m-%d %H:%M:%S} No Device is selected!") - puml(f":Select Device;\nnote right:No Device is selected!\n") - self._reflect_slots() - self.update_widget_states() + try: + if self.config.device: + count = 0 + for device in get_phones(): + if device.id == self.config.device: + self.device_choice.Select(count) + set_phone_id(device.id) + puml(f":Select Device;\n", True) + self._print_device_details(device) + self.update_google_images_menu() + count += 1 + elif self.device_choice.StringSelection: + device = self.device_choice.StringSelection + # replace multiple spaces with a single space and then split on space + id = ' '.join(device.split()) + id = id.split() + id = id[2] + self.config.device = id + for device in get_phones(): + if device.id == id: + set_phone_id(device.id) + puml(f":Select Device;\n", True) + self._print_device_details(device) + self.update_google_images_menu() + else: + set_phone_id(None) + self.device_label.Label = "ADB Connected Devices" + if self.device_choice.StringSelection == '': + set_phone_id(None) + self.device_label.Label = "ADB Connected Devices" + self.config.device = None + print(f"{datetime.now():%Y-%m-%d %H:%M:%S} No Device is selected!") + puml(f":Select Device;\nnote right:No Device is selected!\n") + self._reflect_slots() + self.update_widget_states() + except Exception as e: + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error in function _select_configured_device") + traceback.print_exc() # ----------------------------------------------- # refresh_device @@ -2175,6 +2325,7 @@ def update_widget_states(self): self.pif_info_menu_item: ['device_attached'], self.props_as_json_menu_item: ['device_attached'], self.xml_view_menu_item: ['device_attached'], + self.cancel_ota_menu_item: ['device_attached'], self.push_menu: ['device_attached'], self.push_file_to_tmp_menu: ['device_attached'], self.push_file_to_download_menu: ['device_attached'], @@ -2288,6 +2439,7 @@ def _on_select_device(self, event): if device.id == d_id: set_phone_id(device.id) self._print_device_details(device) + self.update_google_images_menu() self._reflect_slots() self.update_widget_states() except Exception as e: @@ -3689,6 +3841,16 @@ def update_rooted_image(self, is_rooted=False): print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while updating root image") traceback.print_exc() + # ----------------------------------------------- + # _on_show_device_download + # ----------------------------------------------- + def _on_show_device_download(self, event): + device = get_phone() + if not device: + return + menu = GoogleImagesPopupMenu(self, device=device.hardware, date_filter=device.firmware_date) + self.PopupMenu(menu) + #----------------------------------------------------------------------------- # _init_ui #----------------------------------------------------------------------------- @@ -3779,13 +3941,9 @@ def _add_mode_radio_button(sizer, index, flash_mode, label, tooltip): # 5th row widgets, firmware file firmware_label = wx.StaticText(parent=panel, label=u"Device Image") - firmware_button = DropDownButton(parent=panel, id=wx.ID_ANY, bitmap=wx.NullBitmap, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.BU_AUTODRAW) - firmware_button.SetBitmap(images.open_link_24.GetBitmap()) - firmware_button.SetToolTip(u"Download image file for Pixel devices.") - firmware_button.AddLink("Full OTA Images for Pixel Phones / Tablets", FULL_OTA_IMAGES_FOR_PIXEL_DEVICES, images.phone_green_24.GetBitmap()) - firmware_button.AddLink("Factory Images for Pixel Phones / Tablets", FACTORY_IMAGES_FOR_PIXEL_DEVICES, images.phone_blue_24.GetBitmap()) - firmware_button.AddLink("Full OTA Images for Pixel Watches", FULL_OTA_IMAGES_FOR_WATCH_DEVICES, images.watch_green_24.GetBitmap()) - firmware_button.AddLink("Factory Images for Pixel Watches", FACTORY_IMAGES_FOR_WATCH_DEVICES, images.watch_blue_24.GetBitmap()) + self.firmware_button = wx.BitmapButton(parent=panel, id=wx.ID_ANY, bitmap=wx.NullBitmap, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.BU_AUTODRAW) + self.firmware_button.SetBitmap(images.open_link_24.GetBitmap()) + self.firmware_button.SetToolTip(u"Download image file for current Pixel device.") self.firmware_picker = wx.FilePickerCtrl(parent=panel, id=wx.ID_ANY, path=wx.EmptyString, message=u"Select a file", wildcard=u"Factory Image files (*.zip;*.tgz;*.tar)|*.zip;*.tgz;*.tar", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.FLP_USE_TEXTCTRL) self.firmware_picker.SetToolTip(u"Select Pixel Firmware") self.process_firmware = wx.Button(parent=panel, id=wx.ID_ANY, label=u"Process", pos=wx.DefaultPosition, size=wx.DefaultSize, style=0) @@ -3794,7 +3952,7 @@ def _add_mode_radio_button(sizer, index, flash_mode, label, tooltip): firmware_label_sizer = wx.BoxSizer(orient=wx.HORIZONTAL) firmware_label_sizer.Add(window=firmware_label, proportion=0, flag=wx.ALL, border=5) firmware_label_sizer.AddStretchSpacer(1) - firmware_label_sizer.Add(window=firmware_button, proportion=0, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=0) + firmware_label_sizer.Add(window=self.firmware_button, proportion=0, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=0) self.firmware_sizer = wx.BoxSizer(orient=wx.HORIZONTAL) self.firmware_sizer.Add(window=self.firmware_picker, proportion=1, flag=wx.EXPAND) self.firmware_sizer.Add(window=self.process_firmware, flag=wx.LEFT, border=5) @@ -4079,11 +4237,24 @@ def _add_mode_radio_button(sizer, index, flash_mode, label, tooltip): self.Bind(wx.EVT_CLOSE, self._on_close) self.Bind(wx.EVT_SIZE, self._on_resize) self.Bind(wx.EVT_MOVE_END, self._on_move_end) + self.Bind(wx.EVT_BUTTON, self._on_show_device_download, self.firmware_button) # Update UI self.Layout() + # ----------------------------------------------- + # update_google_images_menu + # ----------------------------------------------- + def update_google_images_menu(self): + menu_index = self.menuBar.FindMenu("Google Images") + self.menuBar.Remove(menu_index) + self.google_images_menu = GoogleImagesMenu(self) + self.menuBar.Insert(menu_index, self.google_images_menu, "Google Images") + self.Refresh() + self.Update() + + # ============================================================================ # Class MySplashScreen # ============================================================================ diff --git a/bin/update_engine_client b/bin/update_engine_client new file mode 100644 index 0000000..2ec0a53 Binary files /dev/null and b/bin/update_engine_client differ diff --git a/build-on-linux.spec b/build-on-linux.spec index 2b4e33b..73a1f72 100644 --- a/build-on-linux.spec +++ b/build-on-linux.spec @@ -5,11 +5,11 @@ block_cipher = None a = Analysis(['PixelFlasher.py'], pathex=[], binaries=[('bin/7zzs', 'bin')], - datas=[("images/icon-64.png", "images"), ('bin/busybox_arm64-v8a', 'bin'), ('bin/busybox_armeabi-v7a', 'bin'), ('bin/busybox_x86', 'bin'), ('bin/busybox_x86_64', 'bin'), ('bin/aapt2_arm64-v8a', 'bin'), ('bin/aapt2_armeabi-v7a', 'bin'), ('bin/aapt2_x86', 'bin'), ('bin/aapt2_x86_64', 'bin'), ('bin/avbctl', 'bin')], + datas=[("images/icon-64.png", "images"), ('bin/busybox_arm64-v8a', 'bin'), ('bin/busybox_armeabi-v7a', 'bin'), ('bin/busybox_x86', 'bin'), ('bin/busybox_x86_64', 'bin'), ('bin/aapt2_arm64-v8a', 'bin'), ('bin/aapt2_armeabi-v7a', 'bin'), ('bin/aapt2_x86', 'bin'), ('bin/aapt2_x86_64', 'bin'), ('bin/avbctl', 'bin'), ('bin/update_engine_client', 'bin')], hiddenimports=[], hookspath=[], runtime_hooks=[], - excludes=['bin/busybox_arm64-v8a', 'bin/busybox_armeabi-v7a', 'bin/busybox_x86', 'bin/busybox_x86_64', 'bin/aapt2_arm64-v8a', 'bin/aapt2_armeabi-v7a', 'bin/aapt2_x86', 'bin/aapt2_x86_64', 'bin/avbctl'], + excludes=['bin/busybox_arm64-v8a', 'bin/busybox_armeabi-v7a', 'bin/busybox_x86', 'bin/busybox_x86_64', 'bin/aapt2_arm64-v8a', 'bin/aapt2_armeabi-v7a', 'bin/aapt2_x86', 'bin/aapt2_x86_64', 'bin/avbctl', 'bin/update_engine_client'], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, diff --git a/build-on-mac.spec b/build-on-mac.spec index 68fc040..cda6216 100644 --- a/build-on-mac.spec +++ b/build-on-mac.spec @@ -4,11 +4,11 @@ block_cipher = None a = Analysis(['PixelFlasher.py'], binaries=[('bin/7zz', 'bin')], - datas=[("images/icon-64.png", "images"), ('bin/busybox_arm64-v8a', 'bin'), ('bin/busybox_armeabi-v7a', 'bin'), ('bin/busybox_x86', 'bin'), ('bin/busybox_x86_64', 'bin'), ('bin/aapt2_arm64-v8a', 'bin'), ('bin/aapt2_armeabi-v7a', 'bin'), ('bin/aapt2_x86', 'bin'), ('bin/aapt2_x86_64', 'bin'), ('bin/avbctl', 'bin')], + datas=[("images/icon-64.png", "images"), ('bin/busybox_arm64-v8a', 'bin'), ('bin/busybox_armeabi-v7a', 'bin'), ('bin/busybox_x86', 'bin'), ('bin/busybox_x86_64', 'bin'), ('bin/aapt2_arm64-v8a', 'bin'), ('bin/aapt2_armeabi-v7a', 'bin'), ('bin/aapt2_x86', 'bin'), ('bin/aapt2_x86_64', 'bin'), ('bin/avbctl', 'bin'), ('bin/update_engine_client', 'bin')], hiddenimports=[], hookspath=[], runtime_hooks=[], - excludes=['bin/busybox_arm64-v8a', 'bin/busybox_armeabi-v7a', 'bin/busybox_x86', 'bin/busybox_x86_64', 'bin/aapt2_arm64-v8a', 'bin/aapt2_armeabi-v7a', 'bin/aapt2_x86', 'bin/aapt2_x86_64', 'bin/avbctl'], + excludes=['bin/busybox_arm64-v8a', 'bin/busybox_armeabi-v7a', 'bin/busybox_x86', 'bin/busybox_x86_64', 'bin/aapt2_arm64-v8a', 'bin/aapt2_armeabi-v7a', 'bin/aapt2_x86', 'bin/aapt2_x86_64', 'bin/avbctl', 'bin/update_engine_client'], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher) @@ -28,6 +28,6 @@ exe = EXE(pyz, icon='images/icon-256.icns') app = BUNDLE(exe, name='PixelFlasher.app', - version='6.5.3.1', + version='6.6.0.0', icon='./images/icon-256.icns', bundle_identifier='com.badabing.pixelflasher') diff --git a/build-on-win.spec b/build-on-win.spec index e68cb05..efcfdc7 100644 --- a/build-on-win.spec +++ b/build-on-win.spec @@ -4,11 +4,11 @@ block_cipher = None a = Analysis(['PixelFlasher.py'], binaries=[('bin/7z.exe', 'bin'), ('bin/7z.dll', 'bin')], - datas=[("images/icon-64.png", "images"), ('bin/busybox_arm64-v8a', 'bin'), ('bin/busybox_armeabi-v7a', 'bin'), ('bin/busybox_x86', 'bin'), ('bin/busybox_x86_64', 'bin'), ('bin/aapt2_arm64-v8a', 'bin'), ('bin/aapt2_armeabi-v7a', 'bin'), ('bin/aapt2_x86', 'bin'), ('bin/aapt2_x86_64', 'bin'), ('bin/avbctl', 'bin')], + datas=[("images/icon-64.png", "images"), ('bin/busybox_arm64-v8a', 'bin'), ('bin/busybox_armeabi-v7a', 'bin'), ('bin/busybox_x86', 'bin'), ('bin/busybox_x86_64', 'bin'), ('bin/aapt2_arm64-v8a', 'bin'), ('bin/aapt2_armeabi-v7a', 'bin'), ('bin/aapt2_x86', 'bin'), ('bin/aapt2_x86_64', 'bin'), ('bin/avbctl', 'bin'), ('bin/update_engine_client', 'bin')], hiddenimports=[], hookspath=[], runtime_hooks=[], - excludes=['bin/busybox_arm64-v8a', 'bin/busybox_armeabi-v7a', 'bin/busybox_x86', 'bin/busybox_x86_64', 'bin/aapt2_arm64-v8a', 'bin/aapt2_armeabi-v7a', 'bin/aapt2_x86', 'bin/aapt2_x86_64', 'bin/avbctl'], + excludes=['bin/busybox_arm64-v8a', 'bin/busybox_armeabi-v7a', 'bin/busybox_x86', 'bin/busybox_x86_64', 'bin/aapt2_arm64-v8a', 'bin/aapt2_armeabi-v7a', 'bin/aapt2_x86', 'bin/aapt2_x86_64', 'bin/avbctl', 'bin/update_engine_client'], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher) diff --git a/build.sh b/build.sh index 65e225d..996d2c5 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash rm -rf build dist -VERSION=6.5.3.1 +VERSION=6.6.0.0 NAME="PixelFlasher" DIST_NAME="PixelFlasher" diff --git a/config.py b/config.py index 05e160a..2f5d0b2 100644 --- a/config.py +++ b/config.py @@ -75,6 +75,8 @@ def __init__(self): self.check_for_disk_space = True self.check_for_bootloader_unlocked = True self.check_for_firmware_hash_validity = True + self.google_images_update_frequency = 1 + self.google_images_last_checked = None self.toolbar = { 'tb_position': 'top', @@ -246,6 +248,11 @@ def load(cls, file_path): conf.check_for_bootloader_unlocked = data['check_for_bootloader_unlocked'] with contextlib.suppress(KeyError): conf.check_for_firmware_hash_validity = data['check_for_firmware_hash_validity'] + with contextlib.suppress(KeyError): + conf.google_images_update_frequency = data['google_images_update_frequency'] + with contextlib.suppress(KeyError): + conf.google_images_last_checked = data['google_images_last_checked'] + # read the toolbar section with contextlib.suppress(KeyError): toolbar_data = data['toolbar'] @@ -410,6 +417,8 @@ def save(self, file_path): 'check_for_disk_space': self.check_for_disk_space, 'check_for_bootloader_unlocked': self.check_for_bootloader_unlocked, 'check_for_firmware_hash_validity': self.check_for_firmware_hash_validity, + 'google_images_update_frequency': self.google_images_update_frequency, + 'google_images_last_checked': self.google_images_last_checked, 'toolbar': self.toolbar, # Save the toolbar settings as well 'pif': self.pif, # Save the pif settings as well 'scrcpy': self.scrcpy # Save the scrcpy settings as well diff --git a/constants.py b/constants.py index 5b324b2..b7e9781 100644 --- a/constants.py +++ b/constants.py @@ -2,7 +2,7 @@ APPNAME = 'PixelFlasher' CONFIG_FILE_NAME = 'PixelFlasher.json' -VERSION = '6.5.3.1' +VERSION = '6.6.0.0' SDKVERSION = '33.0.3' MAIN_WIDTH = 1400 MAIN_HEIGHT = 1040 @@ -18,6 +18,8 @@ FULL_OTA_IMAGES_FOR_PIXEL_DEVICES = 'https://developers.google.com/android/ota' FACTORY_IMAGES_FOR_WATCH_DEVICES = 'https://developers.google.com/android/images-watch' FULL_OTA_IMAGES_FOR_WATCH_DEVICES = 'https://developers.google.com/android/ota-watch' +FACTORY_IMAGES_FOR_BETA = 'https://developer.android.com/about/versions/14/download' +FULL_OTA_IMAGES_FOR_BETA = 'https://developer.android.com/about/versions/14/download-ota' PIF_UPDATE_URL = 'https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/update.json' OSM0SIS_PIF_UPDATE_URL = 'https://raw.githubusercontent.com/osm0sis/PlayIntegrityFork/main/update.json' PIF_JSON_PATH = '/data/adb/pif.json' diff --git a/encode-bitmaps.py b/encode-bitmaps.py index a40d659..5711afe 100644 --- a/encode-bitmaps.py +++ b/encode-bitmaps.py @@ -43,6 +43,7 @@ "-a -F -i -n official-16 images/official-16.png images.py", "-a -F -i -n official-24 images/official-24.png images.py", "-a -F -i -n open-link-24 images/open-link-24.png images.py", + "-a -F -i -n open-link-red-24 images/open-link-red-24.png images.py", "-a -F -i -n packages-24 images/packages-24.png images.py", "-a -F -i -n packages-64 images/packages-64.png images.py", "-a -F -i -n partition-24 images/partition-24.png images.py", @@ -121,6 +122,10 @@ "-a -F -i -n heart-red-24 images/heart-red-24.png images.py", "-a -F -i -n heart-gray-24 images/heart-gray-24.png images.py", "-a -F -i -n import-24 images/import-24.png images.py", + "-a -F -i -n cancel-ota-24 images/cancel-ota-24.png images.py", + "-a -F -i -n factory-24 images/factory-24.png images.py", + "-a -F -i -n cloud-24 images/cloud-24.png images.py", + "-a -F -i -n star-green-24 images/star-green-24.png images.py", ] if __name__ == "__main__": diff --git a/images.py b/images.py index 395b8e9..faa66c7 100644 --- a/images.py +++ b/images.py @@ -776,6 +776,18 @@ b'J1iISqZAvggQ/yU5zEmwgA2Ib0AtKSUxE24gNohAloiQUYRsoGppOmgsoKjCQTKcFWoWyVUm' b'sfgWeo22j4RKn1S8D9b8oIUlIDNlAFg+9jMs6bgeAAAAAElFTkSuQmCC') +#---------------------------------------------------------------------- +open_link_red_24 = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1B' + b'AACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAFtSURBVEhLrZaxSgNBEIb3AiGdlZAi' + b'plCwOUELOyvzBqKFgpC3UC5PoJDHEEELwScwnbU2aQJaBDsrOyMkfpOdHIZkL7vrfTCZmb3b' + b'/9/bPZIkk+bWhjHm5vXj+5BcyF6jdp8M386k1nnPzGtK76JCeIkrxwgnWu+vEheSl0pjorWs' + b'cEDq226BMfHAE9xKg1GNdE1sSq+0MF3TekpuoOI7CPxIHwqGGeJX2ubIFs3olyXOYr+0nDOI' + b'Yol4h9Sz3T8NlomzC3IuOdEGPuJClIGH+Lt8MD4MNvBcecb4EfnAyGsqwcRHe82NiM/u1zmZ' + b'XnLi/QSeK1/AyyBUnPvPiRFxt9IgcuUnzKkSp4UGiF/EbAvkuk4DxNdJuViA+BxFTyDfJwOE' + b'x8RljLjgNEBwRNol6tTd6WAEhWcgJsSntlEUGpTBX4OUg61qHY1qpLYL+8n0JeXV3tbayL+K' + b'JwZa2pcKC+7JFrWlsEPlYTVN+xcs9sPXJJYAdgAAAABJRU5ErkJggg==') + #---------------------------------------------------------------------- packages_24 = PyEmbeddedImage( b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACOUlEQVRIS7VVTVbiQBCupuPg' @@ -6163,3 +6175,49 @@ b'BVf/QvArp6V5i3xzsMhMlceH3cql8kpMu8jFhlwP8bUMlstKJ7l6ksrGxhttoteBQhgRIIub' b'XjZyhFsnWWbEVslpr0wXbtpijt7BH6Rh/RkdlLcrAAAAAElFTkSuQmCC') +#---------------------------------------------------------------------- +cancel_ota_24 = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACNElEQVRIS+WV31HbQBDGd8+j' + b'd3jLIB6UCnLXgakguAKcCoAKgArsVBBTAXRgd3CigugBMXlDz0l8m937k5GxLGtgeOJmPGNb' + b'd/vb/b7dE8I7L3zn+PABABaKA6V+nxPhmOstvKSEJaIrv7inm30S90pkR0enSDjjICHw9mrI' + b'4cTA42oXaCfAQq5RwZIPHgDBiivgbKkBWDcwUprBV/xM+4IcnuyCdAJEFlR/fkpw3jB3Lrsx' + b'UHHwzfWg8hkBXPC/jXb1YVcV3QCVX/CDWcg8Ywm2g6dgJeZL9maMQNddnmwALHwqlFJnBDgV' + b'3QlpYtZP931GRp/u2Ph7TY+Tl3v/AzY0j7vIrT8b+FX1AoKcz7tk8oC25kEWuATIqj5p2tBS' + b'5WyFuA0rROD2rfl8WB7woI6uWJbrIZp3VSPVS0IAfzWMqECHX5NcHpCMGqJ5n1ztZ+KnyBsA' + b'KpeWLMhlh0NlScHsKJ8iwbmcZ6NZos0JTwDLGzQ5MAbqcmiWJR7fAdKpFwG8d1paVn7zXJiW' + b'B3Fg2CRN9ckQgE2zEoycJ2MtHI9R0ZJdvzSunm93EcCCK/m+r5KYfSHZ8mfahkRPtUx3aw4C' + b'eV/2fCjJ+sxfFpI5e/ijDUldKXtfTLIfGrk9RUfJbmul4eOgltu6SZK2IXwxFuxNIT68+o2W' + b'siSEb2ZdL2I3+kqCueFuejUgBvTdJ/cQIlVsrFQuV3jJLc9XeNW8CSCQ2E1nUVKB3ILLFmme' + b'3gzY1xT/ABkkFyhU6ypQAAAAAElFTkSuQmCC') + +#---------------------------------------------------------------------- +factory_24 = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABH0lEQVRIS82VzQ3CMAyFbYlI' + b'XLsBI8AGhAlgAzaAShRxgzNFoht0BJigZAPYgBV6DygkiJTwUxIgRfTWFzefHT83CBU/WPH+' + b'4A1AIyJ0soLzgCWQq3cvgPak3sDjcV8AAFos5jt/gIg0ZabbAiAEZYsD8wnoS0B6BUDIFjzx' + b'BxiRISIsKwTUpog40wAJS7I5D28q6IzJUos60FmLSCot1C8AINZZfOjdAC4220mLUW0xV60T' + b'1VYCsGvMFNvEnD4DKC2X2VBlM8PbNk1ZtGEAcgkIygDnOCEgNBtn0QZvA779hcgKzkNcTLI5' + b'6t9urr4vBegFG0QndB9/rz9UUDnAlrnr+v/0wPWsdWVv98A7wPWMbXG/74Eto0/XvdzJr+An' + b'1jHTGaDILiwAAAAASUVORK5CYII=') + +#---------------------------------------------------------------------- +cloud_24 = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABWElEQVRIS+WVwU3DMBSGn1GM' + b'OJYNMkLZwGGCdgLYgFRKK26UK63UjgATwAStNyAbwAi5O8L8TnBQK7txID313aK89z77e3bC' + b'6MjBjtyfTgAgUhpQFN0xxgR0xkYpI51/acrlsnxsU3xQkZidj5jWK9vY0azQWo8Bkj6QFyCy' + b'SGDVrygctK0SkMQHcQKMFsb5R0jzH3ixXahL10LcgIynjJFRExzYxdw1kx2AuL+IqSxvoOb2' + b'gHcnFIN/2yzK8f7LBiCmfIiHbQct+72cmirAH5z71EmozTdPamITakAWPUDLPFh4e2KOoV/V' + b'dwaRTLlRI9rrwjO0polcqrUFmCMZh5cHZVYzsYB3lAyDyjokAYCRIK5nfIUtpR1qQ1KrOfR9' + b'ihqwvXi/96D+9phh9xG5ViqRayp2bzK+QWccuup5dJ1JgZpPqH6hUj2b5s0x7WPJvh4n8Mv8' + b'r75vimB0GVWdah0AAAAASUVORK5CYII=') + +#---------------------------------------------------------------------- +star_green_24 = PyEmbeddedImage( + b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABE0lEQVRIS8WV3w2CMBDGr4n4' + b'7ga6AWxQN3AFRzAR33nGRDcQJzBOQEdgAxmB95LUO7UmIH/aGoAnEnr3+772vsJg4IcN3B/G' + b'BfDQ85F4QVe+ozOhlIrEsRS6vuJgHXopfuCOzXVZnsZy1QZQfzZ/lSPgK7zuYHJA9nHYeUbO' + b'DtDeFhgrmFK3rq10BeRKykCcocBheCBg2QZxAeSoPhKxTKgp3884Y4zGuRFiA8C5hjuUMiHl' + b'dcX8MN/gdp3qIDsAwFUr/wG8nRCgcug2AN0zw4TudEL7Eu8CIFCBhQt66Uu8KwCGHlMST0Gj' + b'w+68r5wdmN5TkwI6E2roIEMHQeN13ZdQA4DAQajkZtxfpoFC6yWDO3gCC9GPGUIPuKoAAAAA' + b'SUVORK5CYII=') + diff --git a/images/cancel-ota-24.png b/images/cancel-ota-24.png new file mode 100644 index 0000000..53de6c5 Binary files /dev/null and b/images/cancel-ota-24.png differ diff --git a/images/cloud-24.png b/images/cloud-24.png new file mode 100644 index 0000000..02797ff Binary files /dev/null and b/images/cloud-24.png differ diff --git a/images/cloud-download-24.png b/images/cloud-download-24.png new file mode 100644 index 0000000..f8f0ff7 Binary files /dev/null and b/images/cloud-download-24.png differ diff --git a/images/factory-24.png b/images/factory-24.png new file mode 100644 index 0000000..50c70b3 Binary files /dev/null and b/images/factory-24.png differ diff --git a/images/google_images.png b/images/google_images.png new file mode 100644 index 0000000..3e3da39 Binary files /dev/null and b/images/google_images.png differ diff --git a/images/open-link-red-24.png b/images/open-link-red-24.png new file mode 100644 index 0000000..a86483b Binary files /dev/null and b/images/open-link-red-24.png differ diff --git a/images/star-green-24.png b/images/star-green-24.png new file mode 100644 index 0000000..4bcd7e5 Binary files /dev/null and b/images/star-green-24.png differ diff --git a/modules.py b/modules.py index c66640a..c34ada2 100644 --- a/modules.py +++ b/modules.py @@ -1914,8 +1914,7 @@ def patch_script(patch_method): print(f" Magisk Version: {m_version}") puml(f"note right\nMagisk Manager Version: {m_app_version}\nMagisk Version: {m_version}\nend note\n") - # Temporary workaround for rooted Magisk Delta, don't ever recommend it. - if is_rooted and self.config.magisk != 'io.github.huskydg.magisk': + if is_rooted: method = 1 # rooted # disable app method if app is not found or is hidden. if not magisk_app_version or ( self.config.magisk not in ['', 'com.topjohnwu.magisk', 'io.github.vvb2060.magisk', 'io.github.huskydg.magisk'] ): @@ -2026,8 +2025,8 @@ def patch_script(patch_method): PixelFlasher will offer available choices and recommend the best method to utilize for patching. Unless you know what you're doing, it is recommended that you take the default suggested selection. ''' - message += f"
Core Magisk Version:          {m_version}\n"
-        message += f"Magisk Application Version:   {m_app_version}\n"
+        message += f"
Core Magisk Version:          {magisk_version}\n"
+        message += f"Magisk Application Version:   {magisk_app_version}\n"
         message += f"Recommended Patch method:     Method {method}
\n" clean_message = message.replace("
", "").replace("
", "").replace("
", "")
         print(f"\n*** Dialog ***\n{clean_message}\n______________\n")
diff --git a/phone.py b/phone.py
index d6d0eb4..adaaa17 100644
--- a/phone.py
+++ b/phone.py
@@ -413,6 +413,17 @@ def build(self):
         except Exception:
             return ''
 
+    # ----------------------------------------------------------------------------
+    #                               property api_level
+    # ----------------------------------------------------------------------------
+    @property
+    def firmware_date(self):
+        if self.build:
+            build_date_match = re.search(r'\b(\d{6})\b', self.build.lower())
+            if build_date_match:
+                build_date = build_date_match[1]
+                return int(build_date)
+
     # ----------------------------------------------------------------------------
     #                               property api_level
     # ----------------------------------------------------------------------------
@@ -485,12 +496,17 @@ def root_symbol(self):
     # ----------------------------------------------------------------------------
     @property
     def magisk_path(self):
-        if self.mode == 'adb' and get_magisk_package():
-            res = self.get_package_path(get_magisk_package(), True)
-            if res != -1:
-                return res
-            self._rooted = None
-            return None
+        try:
+            if self.mode == 'adb' and get_magisk_package():
+                res = self.get_package_path(get_magisk_package(), True)
+                if res != -1:
+                    return res
+                self._rooted = None
+                return None
+        except Exception:
+            print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Could not get magisk path")
+            traceback.print_exc()
+        return None
 
     # ----------------------------------------------------------------------------
     #                               property magisk_version
@@ -609,11 +625,11 @@ def get_verity_verification(self, item):
         if not self.rooted:
             return -1
 
-        res = self.push_avbctl()
-        if res != 0:
-            return -1
-
         try:
+            res = self.push_avbctl()
+            if res != 0:
+                return -1
+
             theCmd = f"\"{get_adb()}\" -s {self.id} shell \"su -c \'/data/local/tmp/avbctl get-{item}\'\""
             print(f"Checking {item} status: ...")
             res = run_shell(theCmd)
@@ -629,6 +645,38 @@ def get_verity_verification(self, item):
             puml(f"#red:ERROR: Could not get {item} status.;\n", True)
             return -1
 
+    # ----------------------------------------------------------------------------
+    #                               method reset_ota_update
+    # ----------------------------------------------------------------------------
+    def reset_ota_update(self):
+        if self.mode != 'adb':
+            return -1
+        if not self.rooted:
+            return -1
+
+        try:
+            res = self.push_update_engine_client()
+            if res != 0:
+                return -1
+
+            print("Cancelling onging OTA update (if one is in progress, ignore errors) ...")
+            theCmd = f"\"{get_adb()}\" -s {self.id} shell \"su -c \'/data/local/tmp/update_engine_client --cancel\'\""
+            res = run_shell2(theCmd)
+            print("Resetting an already applied update (if one exists) ...")
+            theCmd = f"\"{get_adb()}\" -s {self.id} shell \"su -c \'/data/local/tmp/update_engine_client --reset_status\'\""
+            res = run_shell2(theCmd)
+            if res.returncode == 0:
+                return res.stdout
+            print(f"Return Code: {res.returncode}.")
+            print(f"Stdout: {res.stdout}")
+            print(f"Stderr: {res.stderr}")
+            return -1
+        except Exception as e:
+            traceback.print_exc()
+            print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an exception if reset_ota_update function.")
+            puml(f"#red:ERROR: Encountered an exception if reset_ota_update function.;\n", True)
+            return -1
+
     # ----------------------------------------------------------------------------
     #                               method get_vbmeta_details
     # ----------------------------------------------------------------------------
@@ -1641,6 +1689,32 @@ def push_avbctl(self, file_path = "/data/local/tmp/avbctl") -> int:
             puml("#red:Encountered an error while pushing avbctl;\n")
             traceback.print_exc()
 
+    # ----------------------------------------------------------------------------
+    #                               Method push_update_engine_client
+    # ----------------------------------------------------------------------------
+    def push_update_engine_client(self, file_path = "/data/local/tmp/update_engine_client") -> int:
+        try:
+            # Transfer extraction script to the phone
+            if self.architecture in ['armeabi-v7a', 'arm64-v8a']:
+                path_to_update_engine_client = os.path.join(get_bundle_dir(),'bin', 'update_engine_client')
+                res = self.push_file(f"{path_to_update_engine_client}", f"{file_path}")
+                if res != 0:
+                    print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Could not push {file_path}")
+                    return -1
+                # set the permissions.
+                res = self.set_file_permissions(f"{file_path}", "755")
+                if res != 0:
+                    print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Could not set permission on {file_path}")
+                    return -1
+                return 0
+            else:
+                print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: update_engine_client is not available for device architecture: {self.architecture}")
+                return -1
+        except Exception as e:
+            print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Encountered an error while pushing update_engine_client")
+            puml("#red:Encountered an error while pushing update_engine_client;\n")
+            traceback.print_exc()
+
     # ----------------------------------------------------------------------------
     #                               Method get_package_path
     # ----------------------------------------------------------------------------
@@ -1895,9 +1969,13 @@ def is_display_unlocked(self):
     #                               Method stop_magisk
     # ----------------------------------------------------------------------------
     def stop_magisk(self):
-        print("Stopping Magisk ...")
-        with contextlib.suppress(Exception):
-            self.perform_package_action(get_magisk_package(), 'kill')
+        try:
+            print("Stopping Magisk ...")
+            with contextlib.suppress(Exception):
+                self.perform_package_action(get_magisk_package(), 'kill')
+        except Exception as e:
+            print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Exception during stop_magisk")
+            traceback.print_exc()
 
     # ----------------------------------------------------------------------------
     #                               method get_magisk_detailed_modules
diff --git a/pif_manager.py b/pif_manager.py
index 89dd780..a511334 100644
--- a/pif_manager.py
+++ b/pif_manager.py
@@ -289,6 +289,7 @@ def __init__(self, *args, parent=None, config=None, **kwargs):
         self.create_pif_button.Bind(wx.EVT_BUTTON, self.CreatePifJson)
         self.reload_pif_button.Bind(wx.EVT_BUTTON, self.LoadReload)
         self.process_build_prop_button.Bind(wx.EVT_BUTTON, self.ProcessBuildProp)
+        # self.process_build_prop_button.Bind(wx.EVT_BUTTON, self.ProcessBuildPropFolder)
         self.pi_checker_button.Bind(wx.EVT_BUTTON, self.PlayIntegrityCheck)
         self.xiaomi_pif_button.Bind(wx.EVT_BUTTON, self.XiaomiPif)
         self.freeman_pif_button.Bind(wx.EVT_BUTTON, self.FreemanPif)
@@ -977,6 +978,66 @@ def ProcessBuildProp(self, e):
             traceback.print_exc()
         self._on_spin('stop')
 
+    # -----------------------------------------------
+    #                  ProcessBuildPropFolder
+    # -----------------------------------------------
+    def ProcessBuildPropFolder(self, e):
+        # sourcery skip: dict-assign-update-to-union
+        print(f"{datetime.now():%Y-%m-%d %H:%M:%S} User pressed Process build.props Folder")
+
+        with wx.DirDialog(self, "Select folder to bulk process props files", style=wx.DD_DEFAULT_STYLE) as folderDialog:
+            if folderDialog.ShowModal() == wx.ID_CANCEL:
+                print("User cancelled folder selection.")
+                return
+            selected_folder = folderDialog.GetPath()
+
+        try:
+            self._on_spin('start')
+            prop_files = [file for file in os.listdir(selected_folder) if file.endswith(".prop")]
+            for prop in prop_files:
+                prop_path = os.path.join(selected_folder, prop)
+                processed_dict = {}
+                with open(prop_path, 'r', encoding='ISO-8859-1', errors="replace") as f:
+                    content = f.readlines()
+
+                contentList = [x.strip().split('#')[0].split('=', 1) for x in content if '=' in x.split('#')[0]]
+                contentDict = dict(contentList)
+
+                # Update processed_dict with entries from the current file
+                # In place Union operator below fails on Windows 2019 build, so use the update method instead.
+                # processed_dict |= contentDict
+                processed_dict.update(contentDict)
+
+                # Apply the substitution to the values in processed_dict
+                for k, v in contentDict.items():
+                    for x in v.split('$')[1:]:
+                        key = re.findall(r'\w+', x)[0]
+                        v = v.replace(f'${key}', processed_dict[key])
+                    processed_dict[k] = v.strip()
+
+                json_string = process_dict(processed_dict, self.add_missing_keys_checkbox.IsChecked(), self.advanced_props_support, self.first_api)
+
+                # not needed if we don't want to auto-fill first api
+                json_dict = json5.loads(json_string)
+                keys = ['FIRST_API_LEVEL', 'DEVICE_INITIAL_SDK_INT', '*api_level', 'ro.product.first_api_level']
+                first_api = get_first_match(json_dict, keys)
+                json_string = json.dumps(json_dict, indent=4)
+                processed_dict = self.load_json_with_rules(json_string, self.advanced_props_support)
+                if first_api == '':
+                    donor_json_string = process_dict(processed_dict, self.add_missing_keys_checkbox.IsChecked(), self.advanced_props_support, self.first_api_value)
+                else:
+                    donor_json_string = process_dict(processed_dict, self.add_missing_keys_checkbox.IsChecked(), self.advanced_props_support, None)
+
+                # save json file
+                json_path = os.path.splitext(prop_path)[0] + ".json"
+                with open(json_path, 'w', encoding="ISO-8859-1", errors="replace", newline='\n') as f:
+                    f.write(donor_json_string)
+
+        except Exception:
+            print(f"Cannot process file: '{selected_folder}'.")
+            traceback.print_exc()
+        self._on_spin('stop')
+
     # -----------------------------------------------
     #                  ConsoleStcChange
     # -----------------------------------------------
diff --git a/requirements.txt b/requirements.txt
index d694751..5c634d6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,4 +13,5 @@ bsdiff4>=1.1.5
 lz4>=4.3.2
 psutil>=5.9.5
 json5>=0.9.14
+bs4>=0.0.1
 wxPython>=4.2.0
diff --git a/runtime.py b/runtime.py
index 09f231d..f934430 100644
--- a/runtime.py
+++ b/runtime.py
@@ -8,6 +8,7 @@
 import io
 import json
 import json5
+import math
 import ntpath
 import os
 import re
@@ -29,8 +30,8 @@
 from os import path
 from urllib.parse import urlparse
 import xml.etree.ElementTree as ET
-from urllib.request import urlopen
-
+import urllib.request
+from bs4 import BeautifulSoup
 
 import lz4.frame
 import requests
@@ -407,10 +408,10 @@ def get_boot_images_dir():
 # ============================================================================
 def get_factory_images_dir():
     # factory_images only changed after version 5
-    if parse(VERSION) < parse('7.0.0'):
+    if parse(VERSION) < parse('9.0.0'):
         return 'factory_images'
     else:
-        return 'factory_images7'
+        return 'factory_images9'
 
 
 # ============================================================================
@@ -420,10 +421,10 @@ def get_pf_db():
     # we have different db schemas for each of these versions
     if parse(VERSION) < parse('4.0.0'):
         return 'PixelFlasher.db'
-    elif parse(VERSION) < parse('7.0.0'):
+    elif parse(VERSION) < parse('9.0.0'):
         return 'PixelFlasher4.db'
     else:
-        return 'PixelFlasher7.db'
+        return 'PixelFlasher9.db'
 
 
 # ============================================================================
@@ -2032,29 +2033,183 @@ def get_printable_memory():
     return f"Available Free Memory: {formatted_free_memory} / {formatted_total_memory}"
 
 
+# ============================================================================
+#                               Function device_has_update
+# ============================================================================
+def device_has_update(data, device_id, target_date):
+    if not data:
+        return False
+    if device_id in data:
+        device_data = data[device_id]
+
+        for download_type in ['ota', 'factory']:
+            for download_entry in device_data[download_type]:
+                download_date = download_entry['date']
+                # Compare the download date with the target date
+                if download_date > target_date:
+                    return True
+    return False
+
+
+# ============================================================================
+#                               Function get_google_images
+# ============================================================================
+def get_google_images(save_to=None):
+    COOKIE = {'Cookie': 'devsite_wall_acks=nexus-ota-tos,nexus-image-tos,watch-image-tos,watch-ota-tos'}
+    data = {}
+
+    if save_to is None:
+        save_to = os.path.join(get_config_path(), "google_images.json").strip()
+
+    for image_type in ['ota', 'factory', 'ota-watch', 'factory-watch']:
+        if image_type == 'ota':
+            url = "https://developers.google.com/android/ota"
+            download_type = "ota"
+            device_type = "phone"
+        elif image_type == 'factory':
+            url = "https://developers.google.com/android/images"
+            download_type = "factory"
+            device_type = "phone"
+        elif image_type == 'ota-watch':
+            url = "https://developers.google.com/android/ota-watch"
+            download_type = "ota"
+            device_type = "watch"
+        elif image_type == 'factory-watch':
+            url = "https://developers.google.com/android/images-watch"
+            download_type = "factory"
+            device_type = "watch"
+
+        html = urllib.request.urlopen(urllib.request.Request(url, headers=COOKIE)).read()
+        soup = BeautifulSoup(html, 'html.parser')
+        marlin_flag = False
+
+        # Find all the 

elements containing device names + device_elements = soup.find_all('h2') + + # Iterate through the device elements + for device_element in device_elements: + # Check if the text of the

element should be skipped + if device_element.text.strip() in ["Terms and conditions", "Updating instructions", "Updating Pixel 6, Pixel 6 Pro, and Pixel 6a devices to Android 13 for the first time", "Use Android Flash Tool", "Flashing instructions"]: + continue + + # Extract the device name from the 'id' attribute + device_id = device_element.get('id') + + # Extract the device label from the text and strip "id" + device_label = device_element.get('data-text').strip('"').split('" for ')[1] + + # Initialize a dictionary to store the device's downloads for both OTA and Factory + downloads_dict = {'ota': [], 'factory': []} + + # Find the element following the

for each device + table = device_element.find_next('table') + + # Find all

elements in the table + rows = table.find_all('tr') + + # For factory images, the table format changes from Marlin onwards + if device_id == 'marlin': + marlin_flag = True + + for row in rows: + # Extract the fields from each element + columns = row.find_all('td') + version = columns[0].text.strip() + + # Different extraction is necessary per type + if image_type in ['ota', 'ota-watch'] or (marlin_flag and image_type == "factory"): + sha256_checksum = columns[2].text.strip() + download_url = columns[1].find('a')['href'] + elif image_type in ['factory', 'factory-watch']: + download_url = columns[2].find('a')['href'] + sha256_checksum = columns[3].text.strip() + + date_match = re.search(r'\b(\d{6})\b', version) + date = None + if date_match: + date = date_match[1] + else: + date = extract_date_from_google_version(version) + + # Create a dictionary for each download + download_info = { + 'version': version, + 'url': download_url, + 'sha256': sha256_checksum, + 'date': date + } + + # Check if the download entry already exists, and only add it if it's a new entry + if download_info not in downloads_dict[download_type]: + downloads_dict[download_type].append(download_info) + + # Add the device name (using 'device_id') and device label (using 'device_label') to the data dictionary + if device_id not in data: + data[device_id] = { + 'label': device_label, + 'type': device_type, + 'ota': [], + 'factory': [] + } + + # Append the downloads to the corresponding list based on download_type + data[device_id]['ota'].extend(downloads_dict['ota']) + data[device_id]['factory'].extend(downloads_dict['factory']) + + # Convert to JSON + json_data = json.dumps(data, indent=2) + + # Save + with open(save_to, 'w', encoding='utf-8') as json_file: + json_file.write(json_data) + + +# ============================================================================ +# extract_date_from_google_version +# ============================================================================ +def extract_date_from_google_version(version_string): + # pattern to find a 3-letter month followed by a year + pattern = re.compile(r'(\b[A-Za-z]{3}\s\d{4}\b)') + match = pattern.search(version_string) + + if match: + date_str = match.group() + date_obj = datetime.strptime(date_str, '%b %Y') + # convert to yymm01 + return date_obj.strftime('%y%m01') + return None + + # ============================================================================ # Function download_file # ============================================================================ -def download_file(url, filename = None): - if url: - print (f"Downloading File: {url}") - try: - response = requests.get(url) - config_path = get_config_path() - if not filename: - filename = os.path.basename(urlparse(url).path) - downloaded_file_path = os.path.join(config_path, 'tmp', filename) - open(downloaded_file_path, "wb").write(response.content) - # check if filename got downloaded - if not os.path.exists(downloaded_file_path): - print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Failed to download file from {url}\n") - print("Aborting ...\n") - return 'ERROR' - except Exception: - print (f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Failed to download file from {url}\n") - traceback.print_exc() +def download_file(url, filename=None, callback=None): + if not url: + return + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} Downloading: {url} ...") + start = time.time() + try: + response = requests.get(url) + config_path = get_config_path() + if not filename: + filename = os.path.basename(urlparse(url).path) + downloaded_file_path = os.path.join(config_path, 'tmp', filename) + open(downloaded_file_path, "wb").write(response.content) + # check if filename got downloaded + if not os.path.exists(downloaded_file_path): + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Failed to download file from {url}\n") + print("Aborting ...\n") return 'ERROR' - return downloaded_file_path + end = time.time() + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} Download: {filename} completed! in {math.ceil(end - start)} seconds") + # Call the callback function if provided + if callback: + callback() + return downloaded_file_path + except Exception: + print(f"\n{datetime.now():%Y-%m-%d %H:%M:%S} ERROR: Failed to download file from {url}\n") + traceback.print_exc() + return 'ERROR' # ============================================================================ @@ -2675,7 +2830,7 @@ def get_xiaomi_pif(): # sourcery skip: move-assign # ============================================================================ -# Function run_shell +# Function get_freeman_pif # ============================================================================ def get_freeman_pif(abi_list=None): print("\n===== PIFS Random Profile/Fingerprint Picker =====\nCopyright (C) MIT License 2023 Nicholas Bissell (TheFreeman193)") @@ -2692,7 +2847,7 @@ def get_freeman_pif(abi_list=None): try: # Try making the request with SSL certificate verification download_file(FREEMANURL, zip_file) - # with urlopen(d_url) as response, open(zip_file, 'wb') as out_file: + # with urllib.request.urlopen(d_url) as response, open(zip_file, 'wb') as out_file: # shutil.copyfileobj(response, out_file) except ssl.SSLCertVerificationError: # Retry with SSL certificate verification disabled @@ -2702,7 +2857,7 @@ def get_freeman_pif(abi_list=None): ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE - with urlopen(d_url, context=ssl_context) as response, open(zip_file, 'wb') as out_file: + with urllib.request.urlopen(d_url, context=ssl_context) as response, open(zip_file, 'wb') as out_file: shutil.copyfileobj(response, out_file) temp_dir = tempfile.mkdtemp() diff --git a/windows-metadata.yaml b/windows-metadata.yaml index 99a34b4..234b6bb 100644 --- a/windows-metadata.yaml +++ b/windows-metadata.yaml @@ -1,6 +1,6 @@ # https://github.com/DudeNr33/pyinstaller-versionfile # create-version-file windows-metadata.yaml --outfile windows-version-info.txt -Version: 6.5.3.1 +Version: 6.6.0.0 FileDescription: PixelFlasher InternalName: PixelFlasher OriginalFilename: PixelFlasher.exe diff --git a/windows-version-info.txt b/windows-version-info.txt index fd53e5c..b9e9374 100644 --- a/windows-version-info.txt +++ b/windows-version-info.txt @@ -7,8 +7,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. Must always contain 4 elements. - filevers=(6,5,3,1), - prodvers=(6,5,3,1), + filevers=(6,6,0,0), + prodvers=(6,6,0,0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -32,12 +32,12 @@ VSVersionInfo( u'040904B0', [StringStruct(u'CompanyName', u''), StringStruct(u'FileDescription', u'PixelFlasher'), - StringStruct(u'FileVersion', u'6.5.3.1'), + StringStruct(u'FileVersion', u'6.6.0.0'), StringStruct(u'InternalName', u'PixelFlasher'), StringStruct(u'LegalCopyright', u''), StringStruct(u'OriginalFilename', u'PixelFlasher.exe'), StringStruct(u'ProductName', u'PixelFlasher'), - StringStruct(u'ProductVersion', u'6.5.3.1')]) + StringStruct(u'ProductVersion', u'6.6.0.0')]) ]), VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) ]