153 lines
4.7 KiB
Swift
153 lines
4.7 KiB
Swift
//
|
|
// AppRestrictManager.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import DeviceActivity
|
|
import FamilyControls
|
|
import Foundation
|
|
import ManagedSettings
|
|
import UIKit
|
|
|
|
@available(iOS 16.0, *)
|
|
final class AppRestrictManager {
|
|
static let shared = AppRestrictManager()
|
|
|
|
private let center = AuthorizationCenter.shared
|
|
private let activityCenter = DeviceActivityCenter()
|
|
private let activityName = DeviceActivityName(AppRestrictShared.activityName)
|
|
|
|
private init() {}
|
|
|
|
var authorizationStatus: AuthorizationStatus {
|
|
center.authorizationStatus
|
|
}
|
|
|
|
var isAuthorized: Bool {
|
|
center.authorizationStatus == .approved
|
|
}
|
|
|
|
var selection: FamilyActivitySelection {
|
|
get {
|
|
let stored = AppRestrictSharedStore.selection
|
|
let normalized = applicationOnlySelection(stored)
|
|
if !stored.categoryTokens.isEmpty || !stored.webDomainTokens.isEmpty {
|
|
AppRestrictSharedStore.selection = normalized
|
|
}
|
|
return normalized
|
|
}
|
|
set {
|
|
let normalized = applicationOnlySelection(newValue)
|
|
AppRestrictSharedStore.selection = normalized
|
|
// Drop enabled tokens that are no longer in selection
|
|
let apps = normalized.applicationTokens
|
|
AppRestrictSharedStore.enabledTokens = AppRestrictSharedStore.enabledTokens.intersection(apps)
|
|
refreshMonitoringAndShield()
|
|
}
|
|
}
|
|
|
|
var applicationTokens: [ApplicationToken] {
|
|
Array(selection.applicationTokens)
|
|
}
|
|
|
|
var enabledTokens: Set<ApplicationToken> {
|
|
get { AppRestrictSharedStore.enabledTokens }
|
|
set {
|
|
AppRestrictSharedStore.enabledTokens = newValue
|
|
refreshMonitoringAndShield()
|
|
}
|
|
}
|
|
|
|
func requestAuthorization() async throws {
|
|
try await center.requestAuthorization(for: .individual)
|
|
}
|
|
|
|
func mergeSelection(_ incoming: FamilyActivitySelection) {
|
|
var current = selection
|
|
current.applicationTokens.formUnion(incoming.applicationTokens)
|
|
selection = current
|
|
}
|
|
|
|
private func applicationOnlySelection(_ source: FamilyActivitySelection) -> FamilyActivitySelection {
|
|
var result = FamilyActivitySelection(includeEntireCategory: false)
|
|
result.applicationTokens = source.applicationTokens
|
|
return result
|
|
}
|
|
|
|
func isEnabled(_ token: ApplicationToken) -> Bool {
|
|
enabledTokens.contains(token)
|
|
}
|
|
|
|
func setEnabled(_ token: ApplicationToken, enabled: Bool) {
|
|
var set = enabledTokens
|
|
if enabled {
|
|
set.insert(token)
|
|
} else {
|
|
set.remove(token)
|
|
}
|
|
enabledTokens = set
|
|
}
|
|
|
|
func catalogId(for token: ApplicationToken) -> String? {
|
|
AppRestrictSharedStore.catalogId(for: token)
|
|
}
|
|
|
|
func link(catalogId: String, token: ApplicationToken, displayName: String? = nil, iconURL: String? = nil) {
|
|
AppRestrictSharedStore.setLink(
|
|
catalogId: catalogId,
|
|
token: token,
|
|
displayName: displayName,
|
|
iconURL: iconURL
|
|
)
|
|
}
|
|
|
|
func catalogItem(for token: ApplicationToken) -> AppCatalogItem? {
|
|
guard let record = AppRestrictSharedStore.linkRecord(for: token) else { return nil }
|
|
return AppCatalogStore.resolve(link: record)
|
|
}
|
|
|
|
func unlink(_ token: ApplicationToken) {
|
|
AppRestrictSharedStore.removeLink(for: token)
|
|
}
|
|
|
|
func removeApplication(_ token: ApplicationToken) {
|
|
unlink(token)
|
|
var set = enabledTokens
|
|
set.remove(token)
|
|
enabledTokens = set
|
|
|
|
var current = selection
|
|
current.applicationTokens.remove(token)
|
|
selection = current
|
|
}
|
|
|
|
var shieldConfig: AppRestrictShieldConfig {
|
|
get { AppRestrictSharedStore.shieldConfig }
|
|
set { AppRestrictSharedStore.shieldConfig = newValue }
|
|
}
|
|
|
|
@discardableResult
|
|
func saveCustomShieldImage(_ image: UIImage) -> Bool {
|
|
AppRestrictSharedStore.saveCustomImage(image)
|
|
}
|
|
|
|
func refreshMonitoringAndShield() {
|
|
let tokens = enabledTokens
|
|
AppRestrictSharedStore.applyShield(for: tokens)
|
|
activityCenter.stopMonitoring([activityName])
|
|
guard !tokens.isEmpty else { return }
|
|
// Near-daily schedule so Monitor can re-apply after reboot / schedule boundaries.
|
|
let schedule = DeviceActivitySchedule(
|
|
intervalStart: DateComponents(hour: 0, minute: 0),
|
|
intervalEnd: DateComponents(hour: 23, minute: 59),
|
|
repeats: true
|
|
)
|
|
do {
|
|
try activityCenter.startMonitoring(activityName, during: schedule)
|
|
} catch {
|
|
// Shield already applied above; monitoring is best-effort.
|
|
print("[AppRestrict] startMonitoring failed: \(error)")
|
|
}
|
|
}
|
|
}
|