Files

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")