1#!/usr/bin/env python2.5
2
3import cgi
4import codecs
5import os
6import pprint
7import re
8import shutil
9import sys
10import sqlite3
11
12SCREENS = 0
13COLUMNS = 4
14ROWS = 4
15HOTSEAT_SIZE = 4
16CELL_SIZE = 110
17
18CONTAINER_DESKTOP = -100
19CONTAINER_HOTSEAT = -101
20
21DIR = "db_files"
22AUTO_FILE = DIR + "/launcher.db"
23INDEX_FILE = DIR + "/index.html"
24
25def usage():
26  print "usage: print_db.py launcher.db <4x4|5x5|5x6|...> -- prints a launcher.db with"
27  print "       the specified grid size (rows x cols)"
28  print "usage: print_db.py <4x4|5x5|5x6|...> -- adb pulls a launcher.db from a device"
29  print "       and prints it with the specified grid size (rows x cols)"
30  print
31  print "The dump will be created in a directory called db_files in cwd."
32  print "This script will delete any db_files directory you have now"
33
34
35def make_dir():
36  shutil.rmtree(DIR, True)
37  os.makedirs(DIR)
38
39def adb_root_remount():
40  os.system("adb root")
41  os.system("adb remount")
42
43def pull_file(fn):
44  print "pull_file: " + fn
45  rv = os.system("adb pull"
46    + " /data/data/com.android.launcher3/databases/launcher.db"
47    + " " + fn);
48  if rv != 0:
49    print "adb pull failed"
50    sys.exit(1)
51
52def get_favorites(conn):
53  c = conn.cursor()
54  c.execute("SELECT * FROM favorites")
55  columns = [d[0] for d in c.description]
56  rows = []
57  for row in c:
58    rows.append(row)
59  return columns,rows
60
61def get_screens(conn):
62  c = conn.cursor()
63  c.execute("SELECT * FROM workspaceScreens")
64  columns = [d[0] for d in c.description]
65  rows = []
66  for row in c:
67    rows.append(row)
68  return columns,rows
69
70def print_intent(out, id, i, cell):
71  if cell:
72    out.write("""<span class="intent" title="%s">shortcut</span>""" % (
73        cgi.escape(cell, True)
74      ))
75
76
77def print_icon(out, id, i, cell):
78  if cell:
79    icon_fn = "icon_%d.png" % id
80    out.write("""<img style="width: 3em; height: 3em;" src="%s">""" % ( icon_fn ))
81    f = file(DIR + "/" + icon_fn, "w")
82    f.write(cell)
83    f.close()
84
85def print_icon_type(out, id, i, cell):
86  if cell == 0:
87    out.write("Application (%d)" % cell)
88  elif cell == 1:
89    out.write("Shortcut (%d)" % cell)
90  elif cell == 2:
91    out.write("Folder (%d)" % cell)
92  elif cell == 4:
93    out.write("Widget (%d)" % cell)
94  elif cell:
95    out.write("%d" % cell)
96
97def print_cell(out, id, i, cell):
98  if not cell is None:
99    out.write(cgi.escape(unicode(cell)))
100
101FUNCTIONS = {
102  "intent": print_intent,
103  "icon": print_icon,
104  "iconType": print_icon_type
105}
106
107def render_cell_info(out, cell, occupied):
108  if cell is None:
109    out.write("    <td width=%d height=%d></td>\n" %
110        (CELL_SIZE, CELL_SIZE))
111  elif cell == occupied:
112    pass
113  else:
114    cellX = cell["cellX"]
115    cellY = cell["cellY"]
116    spanX = cell["spanX"]
117    spanY = cell["spanY"]
118    intent = cell["intent"]
119    if intent:
120      title = "title=\"%s\"" % cgi.escape(cell["intent"], True)
121    else:
122      title = ""
123    out.write(("    <td colspan=%d rowspan=%d width=%d height=%d"
124        + " bgcolor=#dddddd align=center valign=middle %s>") % (
125          spanX, spanY,
126          (CELL_SIZE*spanX), (CELL_SIZE*spanY),
127          title))
128    itemType = cell["itemType"]
129    if itemType == 0:
130      out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] ))
131      out.write("<br/>\n")
132      out.write(cgi.escape(cell["title"]) + " <br/><i>(app)</i>")
133    elif itemType == 1:
134      out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] ))
135      out.write("<br/>\n")
136      out.write(cgi.escape(cell["title"]) + " <br/><i>(shortcut)</i>")
137    elif itemType == 2:
138      out.write("""<i>folder</i>""")
139    elif itemType == 4:
140      out.write("<i>widget %d</i><br/>\n" % cell["appWidgetId"])
141    else:
142      out.write("<b>unknown type: %d</b>" % itemType)
143    out.write("</td>\n")
144
145def render_screen_info(out, screen):
146  out.write("<tr>")
147  out.write("<td>%s</td>" % (screen["_id"]))
148  out.write("<td>%s</td>" % (screen["screenRank"]))
149  out.write("</tr>")
150
151def process_file(fn):
152  global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE
153  print "process_file: " + fn
154  conn = sqlite3.connect(fn)
155  columns,rows = get_favorites(conn)
156  screenCols, screenRows = get_screens(conn)
157
158  data = [dict(zip(columns,row)) for row in rows]
159  screenData = [dict(zip(screenCols, screenRow)) for screenRow in screenRows]
160
161  # Calculate the proper number of screens, columns, and rows in this db
162  screensIdMap = []
163  hotseatIdMap = []
164  HOTSEAT_SIZE = 0
165  for d in data:
166    if d["spanX"] is None:
167      d["spanX"] = 1
168    if d["spanY"] is None:
169      d["spanY"] = 1
170    if d["container"] == CONTAINER_DESKTOP:
171      if d["screen"] not in screensIdMap:
172        screensIdMap.append(d["screen"])
173      COLUMNS = max(COLUMNS, d["cellX"] + d["spanX"])
174      ROWS = max(ROWS, d["cellX"] + d["spanX"])
175    elif d["container"] == CONTAINER_HOTSEAT:
176      hotseatIdMap.append(d["screen"])
177      HOTSEAT_SIZE = max(HOTSEAT_SIZE, d["screen"] + 1)
178  SCREENS = len(screensIdMap)
179
180  out = codecs.open(INDEX_FILE, encoding="utf-8", mode="w")
181  out.write("""<html>
182<head>
183<style type="text/css">
184.intent {
185  font-style: italic;
186}
187</style>
188</head>
189<body>
190""")
191
192  # Data table
193  out.write("<b>Favorites table</b><br/>\n")
194  out.write("""<html>
195<table border=1 cellspacing=0 cellpadding=4>
196<tr>
197""")
198  print_functions = []
199  for col in columns:
200    print_functions.append(FUNCTIONS.get(col, print_cell))
201  for i in range(0,len(columns)):
202    col = columns[i]
203    out.write("""  <th>%s</th>
204""" % ( col ))
205  out.write("""
206</tr>
207""")
208
209  for row in rows:
210    out.write("""<tr>
211""")
212    for i in range(0,len(row)):
213      cell = row[i]
214      # row[0] is always _id
215      out.write("""  <td>""")
216      print_functions[i](out, row[0], row, cell)
217      out.write("""</td>
218""")
219    out.write("""</tr>
220""")
221  out.write("""</table>
222""")
223
224  # Screens
225  out.write("<br/><b>Screens</b><br/>\n")
226  out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
227  out.write("<tr><td>Screen ID</td><td>Rank</td></tr>\n")
228  for screen in screenData:
229    render_screen_info(out, screen)
230  out.write("</table>\n")
231
232  # Hotseat
233  hotseat = []
234  for i in range(0, HOTSEAT_SIZE):
235    hotseat.append(None)
236  for row in data:
237    if row["container"] != CONTAINER_HOTSEAT:
238      continue
239    screen = row["screen"]
240    hotseat[screen] = row
241  out.write("<br/><b>Hotseat</b><br/>\n")
242  out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
243  for cell in hotseat:
244    render_cell_info(out, cell, None)
245  out.write("</table>\n")
246
247  # Pages
248  screens = []
249  for i in range(0,SCREENS):
250    screen = []
251    for j in range(0,ROWS):
252      m = []
253      for k in range(0,COLUMNS):
254        m.append(None)
255      screen.append(m)
256    screens.append(screen)
257  occupied = "occupied"
258  for row in data:
259    # desktop
260    if row["container"] != CONTAINER_DESKTOP:
261      continue
262    screen = screens[screensIdMap.index(row["screen"])]
263    cellX = row["cellX"]
264    cellY = row["cellY"]
265    spanX = row["spanX"]
266    spanY = row["spanY"]
267    for j in range(cellY, cellY+spanY):
268      for k in range(cellX, cellX+spanX):
269        screen[j][k] = occupied
270    screen[cellY][cellX] = row
271  i=0
272  for screen in screens:
273    out.write("<br/><b>Screen %d</b><br/>\n" % i)
274    out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
275    for m in screen:
276      out.write("  <tr>\n")
277      for cell in m:
278        render_cell_info(out, cell, occupied)
279      out.write("</tr>\n")
280    out.write("</table>\n")
281    i=i+1
282
283  out.write("""
284</body>
285</html>
286""")
287
288  out.close()
289
290def updateDeviceClassConstants(str):
291  global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE
292  match = re.search(r"(\d+)x(\d+)", str)
293  if match:
294    COLUMNS = int(match.group(1))
295    ROWS = int(match.group(2))
296    HOTSEAT_SIZE = 2 * int(COLUMNS / 2)
297    return True
298  return False
299
300def main(argv):
301  if len(argv) == 1 or (len(argv) == 2 and updateDeviceClassConstants(argv[1])):
302    make_dir()
303    adb_root_remount()
304    pull_file(AUTO_FILE)
305    process_file(AUTO_FILE)
306  elif len(argv) == 2 or (len(argv) == 3 and updateDeviceClassConstants(argv[2])):
307    make_dir()
308    process_file(argv[1])
309  else:
310    usage()
311
312if __name__=="__main__":
313  main(sys.argv)
314