I have developed a Flutter application to lock selected apps using the Screen Time API (iOS). My app has been registered in Family Control and currently successfully executes app restrictions in both emulators and devices through TestFlight. I intend to incorporate a countdown timer feature to ensure that the app restriction function is activated when the timer stops (e.g 15 minutes after function called).
However, the issue I'm facing is that on iOS, when the app goes into the background mode, all processes, including the countdown, halt, rendering the restriction function unusable. I have attempted various mechanisms, such as:
- DeviceActivityMonitor extension => Not triggered even though DeviceActivitySchedule is set.
- Cron => Stops when in background mode.
- Local notification => Only triggered when the user interacts with the notification, not automatically execute function in background
Is there a solution to address this matter?
Here is my snippet code, startAppRestrictions() is already work, but I need to started it after 15 minutes (I tried on schedulingRestrictions() function)
import Foundation
import FamilyControls
import ManagedSettings
import DeviceActivity
private let _MyModel = MyModel()
class MyModel: ObservableObject {
let store = ManagedSettingsStore()
@Published var familyActivitySelection: FamilyActivitySelection
var selectAppTitle: String = ""
var cancelButtonTitle: String = ""
var doneButtonTitle: String = ""
init() {
familyActivitySelection = FamilyActivitySelection()
}
class var shared: MyModel {
return _MyModel
}
func startAppRestrictions() {
print("setShieldRestrictions")
// Pull the selection out of the app's model and configure the application shield restriction accordingly
let applications = MyModel.shared.familyActivitySelection
if applications.applicationTokens.isEmpty {
print("empty applicationTokens")
}
if applications.categoryTokens.isEmpty {
print("empty categoryTokens")
}
//lock application
store.shield.applications = applications.applicationTokens.isEmpty ? nil : applications.applicationTokens
store.shield.applicationCategories = applications.categoryTokens.isEmpty ? nil : ShieldSettings.ActivityCategoryPolicy.specific(applications.categoryTokens)
//more rules
store.media.denyExplicitContent = true
//prevent app removal
store.application.denyAppRemoval = true
print("deny app removal: ", store.application.denyAppRemoval ?? false)
//prevent set date time
store.dateAndTime.requireAutomaticDateAndTime = true
}
func stopAppRestrictions(){
//lock application
store.shield.applications = nil
store.shield.applicationCategories = nil
//more rules
store.media.denyExplicitContent = false
//prevent app removal
store.application.denyAppRemoval = false
print("deny app removal: ", store.application.denyAppRemoval ?? false)
//prevent set date time
store.dateAndTime.requireAutomaticDateAndTime = false
}
func isAppLocked() -> Bool {
let isShieldEmpty = (store.shield.applicationCategories == nil);
return !isShieldEmpty
}
func countSelectedAppCategory() -> Int {
let applications = MyModel.shared.familyActivitySelection
return applications.categoryTokens.count
}
func countSelectedApp() -> Int {
let applications = MyModel.shared.familyActivitySelection
return applications.applicationTokens.count
}
func schedulingRestrictions() {
print("Start monitor restriction")
// Schedule restriction 15 minutes after started
let now = Date()
let fiveMinutesLater = Calendar.current.date(byAdding: .minute, value: 15, to: now)
let schedule = DeviceActivitySchedule(intervalStart: Calendar.current.dateComponents([.hour, .minute], from: now),
intervalEnd: Calendar.current.dateComponents([.hour, .minute], from: fiveMinutesLater ?? now),
repeats: true,
warningTime: nil)
let center = DeviceActivityCenter()
do {
try center.startMonitoring(.restrictAppActivityName, during: schedule)
print("Success with Starting Monitor Activity")
}
catch {
print("Error with Starting Monitor Activity: \(error.localizedDescription)")
}
// Setting various properties of the ManagedSettingsStore instance
let applications = MyModel.shared.familyActivitySelection
if applications.applicationTokens.isEmpty {
print("empty applicationTokens")
}
if applications.categoryTokens.isEmpty {
print("empty categoryTokens")
}
print("monitoring model started")
}
}
extension DeviceActivityName {
static let restrictAppActivityName = Self("restrictApp")
}
My DeviceActivityMonitor extension
import UIKit
import MobileCoreServices
import ManagedSettings
import DeviceActivity
class MyMonitor: DeviceActivityMonitor {
override init() {
print("device activity monitor inited")
}
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
print("device activity monitor start")
let model = MyModel.shared
model.stopAppRestrictions()
print("monitor started")
}
override func intervalDidEnd(for activity: DeviceActivityName) {
super.intervalDidEnd(for: activity)
print("device activity monitor end")
let model = MyModel.shared
model.startAppRestrictions()
print("monitor stopped")
}
}