1+ # pylint: disable=W0613
2+ __artifacts_v2__ = {
3+ 'Life360_Drives' : {
4+ 'name' : 'Life360 Drives' ,
5+ 'description' : 'Parses Life360 Drives' ,
6+ 'author' : 'Heather Charpentier' ,
7+ 'creation_date' : '2026-06-10' ,
8+ 'last_update_date' : '2026-06-10' ,
9+ 'requirements' : 'none' ,
10+ 'category' : 'Life360' ,
11+ 'notes' : '' ,
12+ 'paths' : ('*/com.life360.android.safetymapd/databases/DriveBladeDB*' ,),
13+ 'output_types' : 'standard' ,
14+ 'artifact_icon' : 'map-pin'
15+ },
16+ 'Life360_DriveEvents' : {
17+ 'name' : 'Life360 Drive Events' ,
18+ 'description' : 'Parses Life360 Drive Events' ,
19+ 'author' : 'Heather Charpentier' ,
20+ 'creation_date' : '2026-06-10' ,
21+ 'last_update_date' : '2026-06-10' ,
22+ 'requirements' : 'none' ,
23+ 'category' : 'Life360' ,
24+ 'notes' : '' ,
25+ 'paths' : ('*/com.life360.android.safetymapd/databases/DriveBladeDB*' ,),
26+ 'output_types' : ['html' , 'tsv' , 'lava' , 'kml' ],
27+ 'artifact_icon' : 'map-pin'
28+ },
29+ 'Life360_DriveWaypoints' : {
30+ 'name' : 'Life360 Drive Waypoints' ,
31+ 'description' : 'Parses Life360 Drive Waypoints' ,
32+ 'author' : 'Heather Charpentier' ,
33+ 'creation_date' : '2026-06-10' ,
34+ 'last_update_date' : '2026-06-10' ,
35+ 'requirements' : 'none' ,
36+ 'category' : 'Life360' ,
37+ 'notes' : '' ,
38+ 'paths' : ('*/com.life360.android.safetymapd/databases/DriveBladeDB*' ,),
39+ 'output_types' : ['html' , 'tsv' , 'lava' , 'kml' ],
40+ 'artifact_icon' : 'map-pin'
41+ }
42+ }
43+
44+ import datetime
45+ import sqlite3
46+
47+ from scripts .ilapfuncs import artifact_processor , open_sqlite_db_readonly , logfunc
48+
49+
50+ def _ms_to_utc (value ):
51+ if not value :
52+ return ''
53+ try :
54+ return datetime .datetime .fromtimestamp (int (value ) / 1000 , datetime .timezone .utc )
55+ except (ValueError , OverflowError , OSError , TypeError ):
56+ return ''
57+
58+
59+ def _find (context , suffix ):
60+ for file_found in context .get_files_found ():
61+ file_found = str (file_found )
62+ if file_found .endswith (suffix ):
63+ return file_found
64+ return ''
65+
66+
67+ def _q (cursor , sql ):
68+ try :
69+ cursor .execute (sql )
70+ return cursor .fetchall ()
71+ except sqlite3 .Error :
72+ return []
73+
74+
75+ @artifact_processor
76+ def Life360_Drives (context ):
77+ source = _find (context , 'DriveBladeDB' )
78+ data_list = []
79+
80+ if source :
81+ db = open_sqlite_db_readonly (source )
82+ cursor = db .cursor ()
83+
84+ for row in _q (cursor , '''
85+ SELECT
86+ driveId,
87+ userId,
88+ startTime,
89+ endTime,
90+ topSpeed,
91+ topSpeed * 2.23694,
92+ averageSpeed,
93+ averageSpeed * 2.23694,
94+ distance,
95+ duration,
96+ speedingCount,
97+ hardBrakingCount,
98+ rapidAccelerationCount,
99+ distractedCount,
100+ crashCount,
101+ score,
102+ updatedAt
103+ FROM Drives
104+ ORDER BY startTime
105+ ''' ):
106+ data_list .append ((
107+ row [0 ], row [1 ],
108+ _ms_to_utc (row [2 ]), _ms_to_utc (row [3 ]),
109+ row [4 ], row [5 ], row [6 ], row [7 ],
110+ row [8 ], row [9 ],
111+ row [10 ], row [11 ], row [12 ], row [13 ], row [14 ],
112+ row [15 ], _ms_to_utc (row [16 ])
113+ ))
114+ db .close ()
115+
116+ data_headers = (
117+ 'Drive ID' , 'User ID' ,
118+ ('Start Time' , 'datetime' ), ('End Time' , 'datetime' ),
119+ 'Top Speed MPS' , 'Top Speed MPH' ,
120+ 'Average Speed MPS' , 'Average Speed MPH' ,
121+ 'Distance' , 'Duration' ,
122+ 'Speeding Events' , 'Hard Braking Events' ,
123+ 'Rapid Acceleration Events' , 'Distracted Events' , 'Crash Events' ,
124+ 'Score' , ('Updated At' , 'datetime' )
125+ )
126+ return data_headers , data_list , source
127+
128+
129+ @artifact_processor
130+ def Life360_DriveEvents (context ):
131+ source = _find (context , 'DriveBladeDB' )
132+ data_list = []
133+
134+ if source :
135+ db = open_sqlite_db_readonly (source )
136+ cursor = db .cursor ()
137+
138+ for row in _q (cursor , '''
139+ SELECT
140+ DriveEvents.eventId,
141+ DriveEvents.driveId,
142+ Drives.userId,
143+ DriveEvents.eventTime,
144+ DriveEvents.eventType,
145+ DriveEvents.lat,
146+ DriveEvents.lon,
147+ DriveEvents.speed,
148+ DriveEvents.speed * 2.23694,
149+ DriveEvents.accuracy
150+ FROM DriveEvents
151+ LEFT JOIN Drives ON Drives.driveId = DriveEvents.driveId
152+ ORDER BY DriveEvents.eventTime
153+ ''' ):
154+ data_list .append ((
155+ row [0 ], row [1 ], row [2 ],
156+ _ms_to_utc (row [3 ]),
157+ row [4 ], row [5 ], row [6 ],
158+ row [7 ], row [8 ], row [9 ]
159+ ))
160+ db .close ()
161+
162+ data_headers = (
163+ 'Event ID' , 'Drive ID' , 'User ID' ,
164+ ('Event Time' , 'datetime' ),
165+ 'Event Type' , 'Latitude' , 'Longitude' ,
166+ 'Speed MPS' , 'Speed MPH' , 'Accuracy'
167+ )
168+ return data_headers , data_list , source
169+
170+
171+ @artifact_processor
172+ def Life360_DriveWaypoints (context ):
173+ source = _find (context , 'DriveBladeDB' )
174+ data_list = []
175+
176+ if source :
177+ db = open_sqlite_db_readonly (source )
178+ cursor = db .cursor ()
179+
180+ for row in _q (cursor , '''
181+ SELECT
182+ DriveWaypoints.driveId,
183+ Drives.userId,
184+ DriveWaypoints.timestamp,
185+ DriveWaypoints.lat,
186+ DriveWaypoints.lon,
187+ DriveWaypoints.speed,
188+ DriveWaypoints.speed * 2.23694,
189+ DriveWaypoints.accuracy
190+ FROM DriveWaypoints
191+ LEFT JOIN Drives ON Drives.driveId = DriveWaypoints.driveId
192+ ORDER BY DriveWaypoints.timestamp
193+ ''' ):
194+ data_list .append ((
195+ row [0 ], row [1 ],
196+ _ms_to_utc (row [2 ]),
197+ row [3 ], row [4 ],
198+ row [5 ], row [6 ], row [7 ]
199+ ))
200+ db .close ()
201+
202+ data_headers = (
203+ 'Drive ID' , 'User ID' ,
204+ ('Timestamp' , 'datetime' ),
205+ 'Latitude' , 'Longitude' ,
206+ 'Speed MPS' , 'Speed MPH' , 'Accuracy'
207+ )
208+ return data_headers , data_list , source
0 commit comments