DeviceActivityMonitor - intervalDidStart() not able to restrict apps/categories/webCategories

I've followed along with the Screen Time API demos (https://developer.apple.com/videos/play/wwdc2021/10123/)

Also followed along with this guide, which is essentially the same: https://www.folio3.com/mobile/blog/screentime-api-ios/

I'm able to restrict access to apps/categories with the FamilyActivityPicker and FamilyActivitySelection.

I can set a DeviceActivitySchedule, and then use DeviceActivityCenter to start monitoring it.

I can tell that the schedule is working, because MyMonitor:intervalDidStart() does get called, and it works except for the restricting of apps/categories/webCategories.

It's as if MyModel does not have any values set from within MyMonitor. I've confirmed that MyModel does have the correct FamilyActivitySelection apps etc, everywhere else in my Target, before and after the MyMonitor:intervalDidStart() gets called.

MyMonitor is in a separate target called MonitorExtension, that I created using the Device Activity Monitor Extension template. But I made sure to set the Target Membership of MyModel to both my main target, and my extension target.

I have set NSExtensionPrincipalClass to $(PRODUCT_MODULE_NAME).MyMonitor, as suggested.

I have added MyModel.swift to the Compiled Sources in my extensions Build Phases.

I have edited my apps build scheme, to make sure the extension target is also built.

One more interesting thing is that debugger breakpoints and print statements do not work from within my extension. I've even tried caching a string from within MyMonitor:intervalDidStart, and tried to retrieve it afterwards in my main target, but it is nil. Still, I've confirmed that intervalDidStart was actually called by adding any removing store.application.denyAppInstallation = true, and having it work correctly.

I've spent so much time on this problem, any help would be massive..

Here are the files I've referenced:

import UIKit
import MobileCoreServices
import ManagedSettings
import DeviceActivity

class MyMonitor: DeviceActivityMonitor {
  let store = ManagedSettingsStore()

  override func intervalDidStart(for activity: DeviceActivityName) {
    super.intervalDidStart(for: activity)
    let model = MyModel.shared
    let applications = model.selectionToDiscourage.applicationTokens
    let categories = model.selectionToDiscourage.categoryTokens
    let webCategories = model.selectionToDiscourage.webDomainTokens
    
    if applications.isEmpty {
     print("No applications to restrict")
    } else {
     store.shield.applications = applications
    }
    
    if categories.isEmpty {
     print("No categories to restrict")
    } else {
     store.shield.applicationCategories = ShieldSettings.ActivityCategoryPolicy.specific(categories, except: Set())
    }
    
    if webCategories.isEmpty {
     print("No web categories to restrict")
    } else {
     store.shield.webDomains = webCategories
    }

    store.dateAndTime.requireAutomaticDateAndTime = true
    store.account.lockAccounts = true
    store.passcode.lockPasscode = true
    store.siri.denySiri = true
    store.appStore.denyInAppPurchases = true
    store.appStore.maximumRating = 200
    store.appStore.requirePasswordForPurchases = true
    store.media.denyExplicitContent = true
    store.gameCenter.denyMultiplayerGaming = true
    store.media.denyMusicService = true
    store.application.denyAppInstallation = true
  }

  override func intervalDidEnd(for activity: DeviceActivityName) {
    super.intervalDidEnd(for: activity)
    store.shield.applications = nil
    store.shield.applicationCategories = nil
    store.shield.webDomains = nil
    store.dateAndTime.requireAutomaticDateAndTime = false
    store.account.lockAccounts = false
    store.passcode.lockPasscode = false
    store.siri.denySiri = false
    store.appStore.denyInAppPurchases = false
    store.appStore.maximumRating = 1000
    store.appStore.requirePasswordForPurchases = false
    store.media.denyExplicitContent = false
    store.gameCenter.denyMultiplayerGaming = false
    store.media.denyMusicService = false
    store.application.denyAppInstallation = false
  }
}
import Foundation
import FamilyControls
import DeviceActivity
import ManagedSettings

class MyModel: ObservableObject {
  static let shared = MyModel()
  let store = ManagedSettingsStore()

  private init() {}

  var selectionToDiscourage = FamilyActivitySelection() {
    willSet {
      let applications = newValue.applicationTokens
      let categories = newValue.categoryTokens
      let webCategories = newValue.webDomainTokens
      store.shield.applications = applications.isEmpty ? nil : applications
      store.shield.applicationCategories = ShieldSettings.ActivityCategoryPolicy.specific(categories, except: Set())
      store.shield.webDomains = webCategories
     }
  }

  func initiateMonitoring(scheduleStart: DateComponents, scheduleEnd: DateComponents) {
    let schedule = DeviceActivitySchedule(intervalStart: scheduleStart, intervalEnd: scheduleEnd, repeats: true, warningTime: nil)
    print(scheduleStart)
    print(scheduleEnd)
    let center = DeviceActivityCenter()
    do {
      try center.startMonitoring(.daily, during: schedule)
    }
    catch {
      print ("Could not start monitoring \(error)")
    }
    
    store.dateAndTime.requireAutomaticDateAndTime = false
    store.account.lockAccounts = false
    store.passcode.lockPasscode = false
    store.siri.denySiri = false
    store.appStore.denyInAppPurchases = false
    store.appStore.maximumRating = 1000
    store.appStore.requirePasswordForPurchases = false
    store.media.denyExplicitContent = false
    store.gameCenter.denyMultiplayerGaming = false
    store.media.denyMusicService = false
    store.application.denyAppInstallation = false
  }
}

extension DeviceActivityName {
  static let daily = Self("daily")
}
import SwiftUI
import FamilyControls

struct AppPicker: View {
  @StateObject var model = MyModel.shared
  @State var isPresented = false
   
  var body: some View {
    Button("Select Apps to Discourage") {
      isPresented = true
    }
    .familyActivityPicker(isPresented: $isPresented, selection: $model.selectionToDiscourage)
  }
}
  • For more info -

    With the current code, the apps are correctly blocked upon selection in the picker. Then, the schedule is created and they stay blocked, until intervalDidEnd() is called, which lifts the restrictions. But, the next time intervalDidStart() comes around, the apps are not blocked again.

  • I've basically confirmed that :     let applications = model.selectionToDiscourage.applicationTokens     let categories = model.selectionToDiscourage.categoryTokens     let webCategories = model.selectionToDiscourage.webDomainTokens are all not set in MyMonitor, when they are set outside of the extension. Not sure how to fix this.. As MyModel targets the extension

  • You will need to configure an App Group in order to share your app's selectionToDiscourage with its DeviceActivity monitor extension.

    Your print statements will not appear in Xcode's debugger while it it is running your app because the extension runs in a different process. You can use a Logger to print statements to Console instead.

Replies

I've confirmed that MyModel's values are not present in MyMonitor. I'm not sure why though

  • Because your app and your MonitorExtension are separate processes, the shared model isn't actually shared between the two. You will need to configure App Groups in order to share data between your app and its monitor extension.

Add a Comment

did you figure it out as im having the same issue Got my app groups set but cant see how to share the applicationTokens to the extention

Print Statements are not working in Extensions You Need to implement local Notification to test whether these methods are calling or not.

The same Issue i still faced that not geeting the opaque tokens in same extenion

i have faced this issue from last some days. and finally got the solution to share the tokens with Device Monitor Extension