Re: PSA: How to archive your EXACT homescreen(s) apps to Windows for perfect cross-platform backup/restore sans the net
Maria Sophia <[email protected]>
| Newsgroups | comp.mobile.android,alt.comp.microsoft.windows,alt.comp.os.windows-10 |
|---|---|
| Organization | BWH Usenet Archive (https://usenet.blueworldhosting.com) |
| Message-ID | <[email protected]> |
Here is the first working script, with a debug feature added.
It looks at the nova sql database and builds a duplicate hierarchy.
Then it populates that hierarchy with the APK files from the phone.
# -------------------------------------------------------------------
# apkhome.py
# Parse nova.db, build homescreen folder/app skeleton and pull APKs
# python apkhome.py
# -------------------------------------------------------------------
# v1p2 20260819 Added interactive debug mode toggle for dry runs
# v1p1 20260819 Added automated ADB split/monolithic APK pulling
# v1p0 20260819 Combined SQL parsing and direct directory tree generation
# -------------------------------------------------------------------
# Reads nova.db, re-creates the homescreen folder and package hierarchy,
# checks for a connected ADB device, runs adb shell pm path <package>
# to dynamically locate and pull component files (base.apk plus splits)
# feeding results into the newly created matching homescreen hierarchy.
# -------------------------------------------------------------------
import os
import re
import sqlite3
import subprocess
db_file = "nova.db"
base_dir = "./apkhome"
if not os.path.exists(db_file):
print(f"Error: {db_file} not found. Please place it in the working directory.")
exit(1)
# --- DEBUG / LIVE MODE PROMPT ---
print("--- Setup Mode ---")
mode_choice = input("Run in [D]ebug mode (mock files, no phone needed) or [L]ive mode (real ADB pull)? [d/l]: ").strip().lower()
DEBUG_MODE = mode_choice.startswith('d')
adb_active = False
if DEBUG_MODE:
print("\n[!] Running in DEBUG mode. ADB will be bypassed, and dummy APKs will be created.")
else:
print("\n[i] Running in LIVE mode. Checking ADB...")
# Check if ADB is accessible and a device is connected
try:
adb_check = subprocess.run(["adb", "devices"], capture_output=True, text=True, check=True)
devices = [line for line in adb_check.stdout.splitlines() if "\tdevice" in line]
if not devices:
print("Warning: No active ADB devices found. Directory skeleton will be built, but APK pulling will be skipped.")
adb_active = False
else:
print(f"ADB device detected: {devices[0].split()[0]}")
adb_active = True
except (subprocess.SubprocessError, FileNotFoundError):
print("Warning: 'adb' command not found in PATH. Skipping APK pulling.")
adb_active = False
print(f"\nConnecting to {db_file} and processing layout...")
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# 1. Fetch distinct screen indices associated with desktop items
cursor.execute(
"SELECT DISTINCT screen FROM favorites WHERE container = -100 ORDER BY screen ASC;"
)
screens = cursor.fetchall()
for (screen_num,) in screens:
current_panel = f"panel_{screen_num}"
print(f"\n--- Processing: {current_panel} ---")
# 2. Find folders located directly on this specific screen (ignoring blank titles)
cursor.execute(
"SELECT _id, title FROM favorites WHERE container = -100 AND screen = ?"
" AND intent IS NULL AND title IS NOT NULL AND TRIM(title) != '';",
(screen_num,),
)
folders = cursor.fetchall()
for folder_id, folder_name in folders:
clean_folder_name = re.sub(r'[\\/*?:"<>|]', "", folder_name).strip()
if not clean_folder_name:
clean_folder_name = "unnamed_folder"
cursor.execute(
"SELECT title, intent FROM favorites WHERE container = ?;",
(folder_id,),
)
items = cursor.fetchall()
if items:
for app_title, intent in items:
pkg = "Unknown"
if intent and "component=" in intent:
try:
part = intent.split("component=")[1]
pkg = part.split("/")[0]
except IndexError:
pkg = intent
target_path = os.path.join(
base_dir, current_panel, clean_folder_name, pkg
)
os.makedirs(target_path, exist_ok=True)
print(f" [Folder] {clean_folder_name} -> App: {pkg}")
# Pull APK or generate mock file depending on mode
if DEBUG_MODE and pkg != "Unknown":
dummy_apk_path = os.path.join(target_path, "base.apk")
with open(dummy_apk_path, "w") as f:
f.write("mock apk content")
print(f" [Mock ADB] Created test file at {dummy_apk_path}")
elif adb_active and pkg != "Unknown":
path_cmd = subprocess.run(
["adb", "shell", "pm", "path", pkg],
capture_output=True, text=True
)
for line in path_cmd.stdout.splitlines():
if line.startswith("package:"):
remote_path = line.replace("package:", "").strip()
if remote_path:
subprocess.run(
["adb", "pull", remote_path, target_path],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
else:
target_path = os.path.join(base_dir, current_panel, clean_folder_name)
os.makedirs(target_path, exist_ok=True)
# 3. Find loose/top-level apps sitting directly on this screen
cursor.execute(
"SELECT title, intent FROM favorites WHERE container = -100 AND screen = ?"
" AND intent IS NOT NULL;",
(screen_num,),
)
loose_apps = cursor.fetchall()
if loose_apps:
for app_title, intent in loose_apps:
pkg = "Unknown"
if intent and "component=" in intent:
try:
part = intent.split("component=")[1]
pkg = part.split("/")[0]
except IndexError:
pkg = intent
target_path = os.path.join(
base_dir, current_panel, "loose_apps", pkg
)
os.makedirs(target_path, exist_ok=True)
print(f" [Loose App] {pkg}")
if DEBUG_MODE and pkg != "Unknown":
dummy_apk_path = os.path.join(target_path, "base.apk")
with open(dummy_apk_path, "w") as f:
f.write("mock apk content")
print(f" [Mock ADB] Created test file at {dummy_apk_path}")
elif adb_active and pkg != "Unknown":
path_cmd = subprocess.run(
["adb", "shell", "pm", "path", pkg],
capture_output=True, text=True
)
for line in path_cmd.stdout.splitlines():
if line.startswith("package:"):
remote_path = line.replace("package:", "").strip()
if remote_path:
subprocess.run(
["adb", "pull", remote_path, target_path],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
conn.close()
print("\nArchive layout processing complete!")
# end of apkhome.py
--