Skip to content

Commit 52aebbd

Browse files
authored
Updated Support for Life 360
Addresses changes with the databases and the application capabilities itself. Tested on my Life360 test data.
1 parent b408f07 commit 52aebbd

8 files changed

Lines changed: 1133 additions & 0 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
__artifacts_v2__ = {
2+
'Life360_CircleSettings': {
3+
'name': 'Life360 CircleSettings',
4+
'description': 'Parses Life360 Circle Settings',
5+
'author': 'Heather Charpentier',
6+
'creation_date': '2026-06-11',
7+
'last_update_date': '2026-06-12',
8+
'requirements': 'none',
9+
'category': 'Life360',
10+
'notes': '',
11+
'paths': ('*/com.life360.android.safetymapd/databases/L360LocalStoreRoomDatabase*',),
12+
'output_types': 'standard',
13+
'artifact_icon': 'circle'
14+
}
15+
}
16+
17+
from datetime import datetime, timezone
18+
from scripts.ilapfuncs import (
19+
artifact_processor,
20+
get_file_path,
21+
get_sqlite_db_records,
22+
logfunc
23+
)
24+
25+
@artifact_processor
26+
def Life360_CircleSettings(context):
27+
28+
data_list = []
29+
30+
files_found = context.get_files_found()
31+
source_path = get_file_path(files_found, 'L360LocalStoreRoomDatabase')
32+
33+
query = '''
34+
SELECT
35+
je.key AS "Circle ID",
36+
json_extract(je.value, '$.featureSetId') AS "App Features",
37+
json_extract(je.value, '$.featureSetRefId') AS "App Features ID",
38+
json_extract(je.value, '$.features.collision_alerts_push') AS "Collision Alerts Push",
39+
json_extract(je.value, '$.features.collision_alerts_sms') AS "Collision Alerts SMS",
40+
json_extract(je.value, '$.features.customer_support') AS "Customer Support",
41+
json_extract(je.value, '$.features.data_breach_detection') AS "Data Breach Detection",
42+
json_extract(je.value, '$.features.driver_reports') AS "Driver Reports",
43+
json_extract(je.value, '$.features.location_history') AS "Location History",
44+
json_extract(je.value, '$.features.place_alerts') AS "Place Alerts",
45+
json_extract(je.value, '$.features.plan') AS "Plan",
46+
json_extract(je.value, '$.features.sos_alerts_push') AS "SOS Alerts Push",
47+
json_extract(je.value, '$.features.sos_alerts_sms') AS "SOS Alerts SMS",
48+
json_extract(je.value, '$.features.tilegps_activation') AS "TileGPS Activation",
49+
json_extract(je.value, '$.features.uber_one') AS "Uber One"
50+
FROM Premium p
51+
CROSS JOIN json_each(p.circleFeatureSetInfo) je;
52+
'''
53+
54+
try:
55+
db_records = get_sqlite_db_records(source_path, query)
56+
57+
logfunc(f'Life360_CircleSettings: Records found = {len(db_records)}')
58+
59+
for record in db_records:
60+
61+
data_list.append((record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8], record[9], record[10], record[11], record[12], record[13], record[14]))
62+
63+
except Exception as e:
64+
logfunc(f'Error processing Life360 CircleSettings: {e}')
65+
66+
data_headers = ('Circle ID', 'App Features', 'App Features ID', 'Collision Alerts Push', 'Collision Alerts SMS', 'Customer Support', 'Data Breach Detection', 'Driver Reports', 'Location History', 'Place Alerts', 'Plan', 'SOS Alerts Push', 'SOS Alerts SMS', 'TileGPS Activation', 'Uber One')
67+
68+
return data_headers, data_list, source_path
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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

Comments
 (0)