| #!/usr/bin/env python2.5 |
| |
| import cgi |
| import codecs |
| import os |
| import pprint |
| import re |
| import shutil |
| import sys |
| import sqlite3 |
| |
| SCREENS = 0 |
| COLUMNS = 4 |
| ROWS = 4 |
| HOTSEAT_SIZE = 4 |
| CELL_SIZE = 110 |
| |
| CONTAINER_DESKTOP = -100 |
| CONTAINER_HOTSEAT = -101 |
| |
| DIR = "db_files" |
| AUTO_FILE = DIR + "/launcher.db" |
| INDEX_FILE = DIR + "/index.html" |
| |
| def usage(): |
| print "usage: print_db.py launcher.db <4x4|5x5|5x6|...> -- prints a launcher.db with" |
| print " the specified grid size (rows x cols)" |
| print "usage: print_db.py <4x4|5x5|5x6|...> -- adb pulls a launcher.db from a device" |
| print " and prints it with the specified grid size (rows x cols)" |
| print |
| print "The dump will be created in a directory called db_files in cwd." |
| print "This script will delete any db_files directory you have now" |
| |
| |
| def make_dir(): |
| shutil.rmtree(DIR, True) |
| os.makedirs(DIR) |
| |
| def adb_root_remount(): |
| os.system("adb root") |
| os.system("adb remount") |
| |
| def pull_file(fn): |
| print "pull_file: " + fn |
| rv = os.system("adb pull" |
| + " /data/data/com.android.launcher3/databases/launcher.db" |
| + " " + fn); |
| if rv != 0: |
| print "adb pull failed" |
| sys.exit(1) |
| |
| def get_favorites(conn): |
| c = conn.cursor() |
| c.execute("SELECT * FROM favorites") |
| columns = [d[0] for d in c.description] |
| rows = [] |
| for row in c: |
| rows.append(row) |
| return columns,rows |
| |
| def get_screens(conn): |
| c = conn.cursor() |
| c.execute("SELECT * FROM workspaceScreens") |
| columns = [d[0] for d in c.description] |
| rows = [] |
| for row in c: |
| rows.append(row) |
| return columns,rows |
| |
| def print_intent(out, id, i, cell): |
| if cell: |
| out.write("""<span class="intent" title="%s">shortcut</span>""" % ( |
| cgi.escape(cell, True) |
| )) |
| |
| |
| def print_icon(out, id, i, cell): |
| if cell: |
| icon_fn = "icon_%d.png" % id |
| out.write("""<img style="width: 3em; height: 3em;" src="%s">""" % ( icon_fn )) |
| f = file(DIR + "/" + icon_fn, "w") |
| f.write(cell) |
| f.close() |
| |
| def print_icon_type(out, id, i, cell): |
| if cell == 0: |
| out.write("Application (%d)" % cell) |
| elif cell == 1: |
| out.write("Shortcut (%d)" % cell) |
| elif cell == 2: |
| out.write("Folder (%d)" % cell) |
| elif cell == 4: |
| out.write("Widget (%d)" % cell) |
| elif cell: |
| out.write("%d" % cell) |
| |
| def print_cell(out, id, i, cell): |
| if not cell is None: |
| out.write(cgi.escape(unicode(cell))) |
| |
| FUNCTIONS = { |
| "intent": print_intent, |
| "icon": print_icon, |
| "iconType": print_icon_type |
| } |
| |
| def render_cell_info(out, cell, occupied): |
| if cell is None: |
| out.write(" <td width=%d height=%d></td>\n" % |
| (CELL_SIZE, CELL_SIZE)) |
| elif cell == occupied: |
| pass |
| else: |
| cellX = cell["cellX"] |
| cellY = cell["cellY"] |
| spanX = cell["spanX"] |
| spanY = cell["spanY"] |
| intent = cell["intent"] |
| if intent: |
| title = "title=\"%s\"" % cgi.escape(cell["intent"], True) |
| else: |
| title = "" |
| out.write((" <td colspan=%d rowspan=%d width=%d height=%d" |
| + " bgcolor=#dddddd align=center valign=middle %s>") % ( |
| spanX, spanY, |
| (CELL_SIZE*spanX), (CELL_SIZE*spanY), |
| title)) |
| itemType = cell["itemType"] |
| if itemType == 0: |
| out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] )) |
| out.write("<br/>\n") |
| out.write(cgi.escape(cell["title"]) + " <br/><i>(app)</i>") |
| elif itemType == 1: |
| out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] )) |
| out.write("<br/>\n") |
| out.write(cgi.escape(cell["title"]) + " <br/><i>(shortcut)</i>") |
| elif itemType == 2: |
| out.write("""<i>folder</i>""") |
| elif itemType == 4: |
| out.write("<i>widget %d</i><br/>\n" % cell["appWidgetId"]) |
| else: |
| out.write("<b>unknown type: %d</b>" % itemType) |
| out.write("</td>\n") |
| |
| def render_screen_info(out, screen): |
| out.write("<tr>") |
| out.write("<td>%s</td>" % (screen["_id"])) |
| out.write("<td>%s</td>" % (screen["screenRank"])) |
| out.write("</tr>") |
| |
| def process_file(fn): |
| global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE |
| print "process_file: " + fn |
| conn = sqlite3.connect(fn) |
| columns,rows = get_favorites(conn) |
| screenCols, screenRows = get_screens(conn) |
| |
| data = [dict(zip(columns,row)) for row in rows] |
| screenData = [dict(zip(screenCols, screenRow)) for screenRow in screenRows] |
| |
| # Calculate the proper number of screens, columns, and rows in this db |
| screensIdMap = [] |
| hotseatIdMap = [] |
| HOTSEAT_SIZE = 0 |
| for d in data: |
| if d["spanX"] is None: |
| d["spanX"] = 1 |
| if d["spanY"] is None: |
| d["spanY"] = 1 |
| if d["container"] == CONTAINER_DESKTOP: |
| if d["screen"] not in screensIdMap: |
| screensIdMap.append(d["screen"]) |
| COLUMNS = max(COLUMNS, d["cellX"] + d["spanX"]) |
| ROWS = max(ROWS, d["cellX"] + d["spanX"]) |
| elif d["container"] == CONTAINER_HOTSEAT: |
| hotseatIdMap.append(d["screen"]) |
| HOTSEAT_SIZE = max(HOTSEAT_SIZE, d["screen"] + 1) |
| SCREENS = len(screensIdMap) |
| |
| out = codecs.open(INDEX_FILE, encoding="utf-8", mode="w") |
| out.write("""<html> |
| <head> |
| <style type="text/css"> |
| .intent { |
| font-style: italic; |
| } |
| </style> |
| </head> |
| <body> |
| """) |
| |
| # Data table |
| out.write("<b>Favorites table</b><br/>\n") |
| out.write("""<html> |
| <table border=1 cellspacing=0 cellpadding=4> |
| <tr> |
| """) |
| print_functions = [] |
| for col in columns: |
| print_functions.append(FUNCTIONS.get(col, print_cell)) |
| for i in range(0,len(columns)): |
| col = columns[i] |
| out.write(""" <th>%s</th> |
| """ % ( col )) |
| out.write(""" |
| </tr> |
| """) |
| |
| for row in rows: |
| out.write("""<tr> |
| """) |
| for i in range(0,len(row)): |
| cell = row[i] |
| # row[0] is always _id |
| out.write(""" <td>""") |
| print_functions[i](out, row[0], row, cell) |
| out.write("""</td> |
| """) |
| out.write("""</tr> |
| """) |
| out.write("""</table> |
| """) |
| |
| # Screens |
| out.write("<br/><b>Screens</b><br/>\n") |
| out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n") |
| out.write("<tr><td>Screen ID</td><td>Rank</td></tr>\n") |
| for screen in screenData: |
| render_screen_info(out, screen) |
| out.write("</table>\n") |
| |
| # Hotseat |
| hotseat = [] |
| for i in range(0, HOTSEAT_SIZE): |
| hotseat.append(None) |
| for row in data: |
| if row["container"] != CONTAINER_HOTSEAT: |
| continue |
| screen = row["screen"] |
| hotseat[screen] = row |
| out.write("<br/><b>Hotseat</b><br/>\n") |
| out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n") |
| for cell in hotseat: |
| render_cell_info(out, cell, None) |
| out.write("</table>\n") |
| |
| # Pages |
| screens = [] |
| for i in range(0,SCREENS): |
| screen = [] |
| for j in range(0,ROWS): |
| m = [] |
| for k in range(0,COLUMNS): |
| m.append(None) |
| screen.append(m) |
| screens.append(screen) |
| occupied = "occupied" |
| for row in data: |
| # desktop |
| if row["container"] != CONTAINER_DESKTOP: |
| continue |
| screen = screens[screensIdMap.index(row["screen"])] |
| cellX = row["cellX"] |
| cellY = row["cellY"] |
| spanX = row["spanX"] |
| spanY = row["spanY"] |
| for j in range(cellY, cellY+spanY): |
| for k in range(cellX, cellX+spanX): |
| screen[j][k] = occupied |
| screen[cellY][cellX] = row |
| i=0 |
| for screen in screens: |
| out.write("<br/><b>Screen %d</b><br/>\n" % i) |
| out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n") |
| for m in screen: |
| out.write(" <tr>\n") |
| for cell in m: |
| render_cell_info(out, cell, occupied) |
| out.write("</tr>\n") |
| out.write("</table>\n") |
| i=i+1 |
| |
| out.write(""" |
| </body> |
| </html> |
| """) |
| |
| out.close() |
| |
| def updateDeviceClassConstants(str): |
| global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE |
| match = re.search(r"(\d+)x(\d+)", str) |
| if match: |
| COLUMNS = int(match.group(1)) |
| ROWS = int(match.group(2)) |
| HOTSEAT_SIZE = 2 * int(COLUMNS / 2) |
| return True |
| return False |
| |
| def main(argv): |
| if len(argv) == 1 or (len(argv) == 2 and updateDeviceClassConstants(argv[1])): |
| make_dir() |
| adb_root_remount() |
| pull_file(AUTO_FILE) |
| process_file(AUTO_FILE) |
| elif len(argv) == 2 or (len(argv) == 3 and updateDeviceClassConstants(argv[2])): |
| make_dir() |
| process_file(argv[1]) |
| else: |
| usage() |
| |
| if __name__=="__main__": |
| main(sys.argv) |