89 lines
4.2 KiB
Python
Executable File
89 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Rebuild the Nova home screen in Lawnchair, plus the Ice Box shortcuts Nova had pinned.
|
|
|
|
Usage: nova-to-lawnchair.py NOVA_DB LAWNCHAIR_DB OLD_ICEBOX_SHORTCUT_XML OUT_DIR
|
|
|
|
Writes OUT_DIR/launcher_5_5_5.db (Lawnchair schema, Nova's items), OUT_DIR/icebox.xml
|
|
(shortcut_service package file with only the pinned shortcuts) and OUT_DIR/lawnchair-pins.xml
|
|
(shortcut_service launcher file pinning them for Lawnchair).
|
|
"""
|
|
|
|
import re
|
|
import shutil
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
LAUNCHER = "app.lawnchair.nightly"
|
|
ICEBOX = "com.catchingnow.icebox"
|
|
# From /data/system/packages.xml on the new ROM: versionCode, lastUpdateTime, SHA-256 of the cert.
|
|
LAUNCHER_INFO = ("1600020100", "1792700106881", "dHw2RbNXJYsuI+hR5TyWdH/grdAH5bos2X6MhVcuTcU=")
|
|
ICEBOX_INFO = ("154821395", "1792699567277", "gvW+cC4DIBdGDKg3b22r1yWp5S3SSSDdXl17WF+/4bY=")
|
|
# Nova-only containers (gesture/dock-swipe slots); Lawnchair has nothing to put them in.
|
|
SKIP_CONTAINERS = {-207, -208}
|
|
ITEM_APP, ITEM_FOLDER, ITEM_WIDGET, ITEM_DEEP_SHORTCUT = 0, 2, 4, 6
|
|
FLAG_ID_NOT_VALID = 1 # Launcher3 restore flag: allocate and bind a fresh widget id on load
|
|
MISSING_PACKAGES = {"com.dot.gallery"} # not installed on the new ROM
|
|
# Google's smartspace widget on row 0 of the first page: Lawnchair reserves that row for its own.
|
|
SKIP_IDS = {56}
|
|
|
|
nova_db, lc_db, icebox_xml, out = map(Path, sys.argv[1:5])
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def package_info(version: str, updated: str, sig: str) -> str:
|
|
return (f'<package-info version="{version}" last_udpate_time="{updated}" shadow="0" allow-backup="1" '
|
|
f'allow-backup-initialized="1" bk_src_version="-1" bk_src_backup-allowed="0">\n'
|
|
f'<signature hash="{sig}" />\n</package-info>\n')
|
|
|
|
|
|
nova = sqlite3.connect(nova_db)
|
|
nova.row_factory = sqlite3.Row
|
|
rows = [r for r in nova.execute("select * from favorites where _id >= 0 order by _id")
|
|
if r["container"] not in SKIP_CONTAINERS and r["_id"] not in SKIP_IDS]
|
|
folders = {r["_id"] for r in rows if r["itemType"] == ITEM_FOLDER}
|
|
kept = []
|
|
for r in rows:
|
|
pkg = re.search(r"component=([\w.]+)/", r["intent"] or "")
|
|
if pkg and pkg.group(1) in MISSING_PACKAGES:
|
|
print(f"skip {r['title']}: {pkg.group(1)} not installed")
|
|
continue
|
|
if r["container"] >= 0 and r["container"] not in folders:
|
|
continue
|
|
kept.append(r)
|
|
|
|
db = out / "launcher_5_5_5.db"
|
|
shutil.copy(lc_db, db)
|
|
lc = sqlite3.connect(db)
|
|
lc.execute("delete from favorites")
|
|
pins = []
|
|
for r in kept:
|
|
widget = r["itemType"] == ITEM_WIDGET
|
|
hotseat = r["container"] == -101
|
|
in_folder = r["container"] >= 0
|
|
x, y = int(max(r["cellX"] or 0, 0)), int(max(r["cellY"] or 0, 0))
|
|
lc.execute(
|
|
"insert into favorites (_id,title,intent,container,screen,cellX,cellY,spanX,spanY,itemType,"
|
|
"appWidgetId,appWidgetProvider,restored,profileId,rank,options) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
(r["_id"], r["title"], r["intent"], r["container"],
|
|
x if hotseat else (0 if in_folder else r["screen"]),
|
|
x, 0 if hotseat else y, int(r["spanX"]), int(r["spanY"]), r["itemType"],
|
|
r["appWidgetId"] if widget else -1, r["appWidgetProvider"],
|
|
FLAG_ID_NOT_VALID if widget else 0, 0, r["rank"], 0))
|
|
if r["itemType"] == ITEM_DEEP_SHORTCUT:
|
|
pins.append(re.search(r"S\.shortcut_id=([^;]+)", r["intent"]).group(1))
|
|
lc.commit()
|
|
print(f"{len(kept)} items written, pins: {pins}")
|
|
|
|
old = Path(icebox_xml).read_text()
|
|
blocks = [m.group(0) for m in re.finditer(r'<shortcut id="([^"]+)".*?</shortcut>\n', old, re.S) if m.group(1) in pins]
|
|
assert len(blocks) == len(pins), f"found {len(blocks)} of {len(pins)} shortcuts"
|
|
head = re.search(r"<package name=[^>]*>\n", old).group(0)
|
|
(out / "icebox.xml").write_text("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + head
|
|
+ package_info(*ICEBOX_INFO) + "".join(blocks) + "</package>\n")
|
|
(out / "lawnchair-pins.xml").write_text(
|
|
"<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
|
|
f'<launcher-pins package-name="{LAUNCHER}" launcher-user="0">\n' + package_info(*LAUNCHER_INFO)
|
|
+ f'<package package-name="{ICEBOX}" package-user="0">\n'
|
|
+ "".join(f'<pin value="{p}" />\n' for p in pins) + "</package>\n</launcher-pins>\n")
|