@@ -238,6 +238,25 @@ func executeGenericAction(
238238 // required for it to have time to trigger before process/callback ends
239239 sleep ( ms: 1000 )
240240 }
241+ } else if type == " startMonitoring " {
242+ if let activityName = action [ " activityName " ] as? String ,
243+ let deviceActivityEvents = action [ " deviceActivityEvents " ] as? [ [ String : Any ] ] {
244+
245+ startMonitoringAction (
246+ activityName: activityName,
247+ deviceActivityEvents: deviceActivityEvents,
248+ intervalStartDelayMs: action [ " intervalStartDelayMs " ] as? Int ,
249+ intervalEndDelayMs: action [ " intervalEndDelayMs " ] as? Int ,
250+ triggeredBy: triggeredBy
251+ )
252+ }
253+ } else if type == " stopMonitoring " {
254+ let activityNames = action [ " activityNames " ] as? [ String ]
255+
256+ stopMonitoringAction (
257+ activityNames: activityNames,
258+ triggeredBy: triggeredBy
259+ )
241260 }
242261
243262 if let sleepAfter = action [ " sleepAfter " ] as? Int {
@@ -1423,3 +1442,114 @@ func getAppGroupDirectory() -> URL? {
14231442 }
14241443 return nil
14251444}
1445+
1446+ @available ( iOS 15 . 0 , * )
1447+ func startMonitoringAction(
1448+ activityName: String ,
1449+ deviceActivityEvents: [ [ String : Any ] ] ,
1450+ intervalStartDelayMs: Int ? ,
1451+ intervalEndDelayMs: Int ? ,
1452+ triggeredBy: String
1453+ ) {
1454+ // Create date components for schedule
1455+ let now = Date ( )
1456+ let calendar = Calendar . current
1457+
1458+ var intervalStart = DateComponents ( )
1459+ var intervalEnd = DateComponents ( )
1460+
1461+ if let startDelayMs = intervalStartDelayMs {
1462+ let startDate = now. addingTimeInterval ( TimeInterval ( startDelayMs) / 1000.0 )
1463+ intervalStart = calendar. dateComponents ( [ . hour, . minute, . second] , from: startDate)
1464+ }
1465+
1466+ if let endDelayMs = intervalEndDelayMs {
1467+ let endDate = now. addingTimeInterval ( TimeInterval ( endDelayMs) / 1000.0 )
1468+ intervalEnd = calendar. dateComponents ( [ . hour, . minute, . second] , from: endDate)
1469+ } else {
1470+ // Default to 24 hours from start if not specified
1471+ let defaultEndMs = ( intervalStartDelayMs ?? 0 ) + ( 24 * 60 * 60 * 1000 )
1472+ let endDate = now. addingTimeInterval ( TimeInterval ( defaultEndMs) / 1000.0 )
1473+ intervalEnd = calendar. dateComponents ( [ . hour, . minute, . second] , from: endDate)
1474+ }
1475+
1476+ let schedule = DeviceActivitySchedule (
1477+ intervalStart: intervalStart,
1478+ intervalEnd: intervalEnd,
1479+ repeats: false
1480+ )
1481+
1482+ // Create DeviceActivityEvent dictionary
1483+ var eventDict : [ DeviceActivityEvent . Name : DeviceActivityEvent ] = [ : ]
1484+
1485+ for eventData in deviceActivityEvents {
1486+ guard let eventName = eventData [ " eventName " ] as? String ,
1487+ let threshold = eventData [ " threshold " ] as? [ String : Any ] ,
1488+ let familyActivitySelection = eventData [ " familyActivitySelection " ] as? String
1489+ else {
1490+ continue
1491+ }
1492+
1493+ let selection = deserializeFamilyActivitySelection (
1494+ familyActivitySelectionStr: familyActivitySelection)
1495+
1496+ // Convert threshold to DateComponents
1497+ var thresholdComponents = DateComponents ( )
1498+ if let hour = threshold [ " hour " ] as? Int { thresholdComponents. hour = hour }
1499+ if let minute = threshold [ " minute " ] as? Int { thresholdComponents. minute = minute }
1500+ if let second = threshold [ " second " ] as? Int { thresholdComponents. second = second }
1501+
1502+ let includesPastActivity = eventData [ " includesPastActivity " ] as? Bool ?? false
1503+
1504+ var event : DeviceActivityEvent
1505+ if #available( iOS 17 . 4 , * ) {
1506+ event = DeviceActivityEvent (
1507+ applications: selection. applicationTokens,
1508+ categories: selection. categoryTokens,
1509+ webDomains: selection. webDomainTokens,
1510+ threshold: thresholdComponents,
1511+ includesPastActivity: includesPastActivity
1512+ )
1513+ } else {
1514+ event = DeviceActivityEvent (
1515+ applications: selection. applicationTokens,
1516+ categories: selection. categoryTokens,
1517+ webDomains: selection. webDomainTokens,
1518+ threshold: thresholdComponents
1519+ )
1520+ }
1521+
1522+ eventDict [ DeviceActivityEvent . Name ( eventName) ] = event
1523+ }
1524+
1525+ let activityName = DeviceActivityName ( activityName)
1526+
1527+ do {
1528+ try center. startMonitoring ( activityName, during: schedule, events: eventDict)
1529+ logger. log (
1530+ " ✅ Successfully started monitoring activity: \( activityName. rawValue) from \( triggeredBy) " )
1531+ } catch {
1532+ logger. log (
1533+ " ❌ Failed to start monitoring activity: \( activityName. rawValue, privacy: . public) - \( error. localizedDescription, privacy: . public) "
1534+ )
1535+ }
1536+ }
1537+
1538+ @available ( iOS 15 . 0 , * )
1539+ func stopMonitoringAction(
1540+ activityNames: [ String ] ? ,
1541+ triggeredBy: String
1542+ ) {
1543+ if let activityNames = activityNames {
1544+ // Stop specific activities
1545+ let deviceActivityNames = activityNames. map { DeviceActivityName ( $0) }
1546+ center. stopMonitoring ( deviceActivityNames)
1547+ logger. log (
1548+ " ✅ Successfully stopped monitoring activities: \( activityNames. joined ( separator: " , " ) ) from \( triggeredBy) "
1549+ )
1550+ } else {
1551+ // Stop all monitoring
1552+ center. stopMonitoring ( )
1553+ logger. log ( " ✅ Successfully stopped all monitoring from \( triggeredBy) " )
1554+ }
1555+ }
0 commit comments