Swift 使用家庭控制来限制应用程序并获取应用程序名称

问题描述 投票:0回答:1

我正在尝试使用家庭控制模块来限制应用程序的屏幕时间。限制器工作正常,但我希望能够看到我的 Flutter 应用程序中受限制的应用程序的名称。我已经尝试了所有方法,但不知道如何获取这些应用程序的捆绑 ID。 我知道这是可能的,因为有应用程序拦截器可以执行此操作。

    for (index, token) in applications.applicationTokens.enumerated() {
        NSLog("Debug: test 1: \(applications)")
        NSLog("Debug: test 2: \(type(of: applications.applicationTokens.enumerated()))")
        NSLog("Debug: index: \(index)")
        NSLog("Debug: token: \(token)")
    }

此代码给出以下输出:

调试:测试 1:FamilyActivitySelection(includeEntireCategory: false, applicationTokens: Set([ApplicationToken(data: 128 bytes: `D^R'ä´Åyñ xnÆøH4sr;Øý³¶ï(DA^_ÒÖÜä~ Å^A´u^O^ÎÕ^WÆG.aBÍEiÿ_ n!jÜj¤U^Hi=ý^[,H2v7cî·(o[Þò¤¤Þ^[ã/^Ní;ÿÙL ^P=Ý ^]®+>¹·Á )])、categoryTokens:设置([])、webDomainTokens:设置([])、 untokenizedApplicationIdentifiers:设置([]),untokenizedCategoryIdentifiers:设置([]),untokenizedWebDomainIdentifiers:设置([]))

调试:测试 2:EnumeratedSequence>

调试:索引:0

调试:令牌:ApplicationToken(数据:128字节)

这是我的一些代码(我添加了很多代码,因为我不知道发生了什么):

DeviceActivityMonitorExtension.swift:

typealias ApplicationToken = ManagedSettings.Token<ManagedSettings.Application>
typealias ActivityCategoryToken = ManagedSettings.Token<ManagedSettings.ActivityCategory>

class DeviceActivityMonitorExtension: DeviceActivityMonitor {
    override func intervalDidStart(for activity: DeviceActivityName) {
        super.intervalDidStart(for: activity)
    }
    
    override func intervalDidEnd(for activity: DeviceActivityName) {
        super.intervalDidEnd(for: activity)
    }
    
        override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
        super.eventDidReachThreshold(event, activity: activity)
        
        NSLog("Threshold reached for event: \(event.rawValue)")
        
        // Retrieve stored tokens from shared UserDefaults
        guard let defaults = UserDefaults(suiteName: appGroupID) else {
            NSLog("No app group defaults available.")
            return
        }
        
        let decoder = JSONDecoder()
        var applicationTokens: [ApplicationToken] = []
        var categoryTokens: [ActivityCategoryToken] = []
        
        // Decode application tokens from the new key
        if let appTokensData = defaults.data(forKey: Constants.limitedApplicationTokensKey) {
            if let tokens = try? decoder.decode([ApplicationToken].self, from: appTokensData) {
                applicationTokens = tokens
                NSLog("Decoded \(tokens.count) application tokens.")
            } else {
                NSLog("Failed to decode application tokens.")
            }
        } else {
            NSLog("No application tokens found in UserDefaults.")
        }
        
        // Decode category tokens if available
        if let catTokensData = defaults.data(forKey: Constants.limitedCategoryIdentifiersKey) {
            if let cats = try? decoder.decode([ActivityCategoryToken].self, from: catTokensData) {
                categoryTokens = cats
                NSLog("Decoded \(cats.count) category tokens.")
            } else {
                NSLog("Failed to decode category tokens.")
            }
        } else {
            NSLog("No category tokens found in UserDefaults.")
        }

        // Apply restrictions now that the threshold is reached
        let store = ManagedSettingsStore()
        
        // If we have no application tokens, set nil; otherwise, set them as a Set
        store.shield.applications = applicationTokens.isEmpty ? nil : Set(applicationTokens)
        
        // For categories, we use a specific policy if we have any
        store.shield.applicationCategories = categoryTokens.isEmpty
            ? nil
            : ShieldSettings.ActivityCategoryPolicy.specific(Set(categoryTokens))
        
        NSLog("Restrictions applied. The selected apps and categories should now show the Restricted screen.")
    }

    override func intervalWillStartWarning(for activity: DeviceActivityName) {
        super.intervalWillStartWarning(for: activity)
    }
    
    override func intervalWillEndWarning(for activity: DeviceActivityName) {
        super.intervalWillEndWarning(for: activity)
    }
    
    override func eventWillReachThresholdWarning(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
        super.eventWillReachThresholdWarning(event, activity: activity)
    }
}

我的模特:

import Foundation
import FamilyControls
import ManagedSettings

private let _MyModel = MyModel()

class MyModel: ObservableObject {
    let store = ManagedSettingsStore()
    
    @Published var selectionToDiscourage: FamilyActivitySelection
    @Published var selectionToEncourage: FamilyActivitySelection
    @Published var selectionToLimit: FamilyActivitySelection
    
    init() {
        selectionToDiscourage = FamilyActivitySelection()
        selectionToEncourage = FamilyActivitySelection()
        selectionToLimit = FamilyActivitySelection()
    }
    
    class var shared: MyModel {
        return _MyModel
    }
    func setShieldRestrictions() {
        NSLog("Debug: Setting shield restrictions for discouraged apps: \(selectionToDiscourage.applicationTokens)")
        let applications = self.selectionToDiscourage
        store.shield.applications = applications.applicationTokens.isEmpty ? nil : applications.applicationTokens
        store.shield.applicationCategories = applications.categoryTokens.isEmpty
            ? nil
            : ShieldSettings.ActivityCategoryPolicy.specific(applications.categoryTokens)
    }

    // Used to encode codable to UserDefaults
    private let encoder = PropertyListEncoder()

    // Used to decode codable from UserDefaults
    private let decoder = PropertyListDecoder()
    
    func saveFamilyActivitySelection(selection: FamilyActivitySelection) {
        NSLog("Debug: selected app updated: ", selection.applicationTokens.count," category: ", selection.categoryTokens.count)
        let defaults = UserDefaults.standard

        defaults.set(
            try? encoder.encode(selection),
            forKey: "limitedApplicationTokens"
        )
        
        //check is data saved to user defaults
        getSavedFamilyActivitySelection()
    }
    
    //get saved family activity selection from UserDefault
    func getSavedFamilyActivitySelection() -> FamilyActivitySelection? {
        let defaults = UserDefaults.standard
        guard let data = defaults.data(forKey: "limitedApplicationTokens") else {
            return nil
        }
        var selectedApp: FamilyActivitySelection?
        let decoder = PropertyListDecoder()
        selectedApp = try? decoder.decode(FamilyActivitySelection.self, from: data)
        
        NSLog("Debug: saved selected app updated: ", selectedApp?.categoryTokens.count ?? "0")
        return selectedApp
    }

}

AppDelegate.swift:

func clearLimitedAppsData() {
    guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else { return }
    defaults.removeObject(forKey: Constants.limitedApplicationBundleIdentifiersKey)
    defaults.synchronize()
    NSLog("Debug: Cleared limitedApplicationBundleIdentifiersKey data.")
}

var globalMethodCall = ""

@main
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        // clearLimitedAppsData()

        guard let controller = window?.rootViewController as? FlutterViewController else {
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
        
        let METHOD_CHANNEL_NAME = "flutter_screentime"
        
        let model = MyModel.shared
        let store = ManagedSettingsStore()
        
        let methodChannel = FlutterMethodChannel(name: METHOD_CHANNEL_NAME, binaryMessenger: controller.binaryMessenger)
        
        methodChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
            Task {
                // Request authorization for Family Controls if available
                if #available(iOS 16.0, *) {
                    do {
                        try await AuthorizationCenter.shared.requestAuthorization(for: .individual)
                        NSLog("Debug: Authorization request succeeded.")
                    } catch {
                        NSLog("Debug: Authorization request failed with error: \(error)")
                        result(FlutterError(code: "AUTH_ERROR", message: "Authorization failed", details: nil))
                        return
                    }
                } else {
                    NSLog("Debug: iOS version is below 16.0, cannot proceed.")
                    result(FlutterError(code: "iOS_VERSION_ERROR", message: "iOS 16.0 or newer is required", details: nil))
                    return
                }
                
                switch call.method {
                case "blockApp":
                    globalMethodCall = "selectAppsToDiscourage"
                    NSLog("Debug: Presenting blockApp picker.")
                    let vc = UIHostingController(rootView: ContentView()
                        .environmentObject(model)
                        .environmentObject(store))
                    controller.present(vc, animated: true, completion: nil)
                    result("blockApp invoked")
                    
                case "unblockApp":
                    globalMethodCall = "selectAppsToEncourage"
                    NSLog("Debug: Presenting unblockApp picker.")
                    let vc = UIHostingController(rootView: ContentView()
                        .environmentObject(model)
                        .environmentObject(store))
                    controller.present(vc, animated: true, completion: nil)
                    result("unblockApp invoked")
                    
                case "limitApp":
                    globalMethodCall = "selectAppsToLimit"
                    NSLog("Debug: Presenting limitApp picker.")
                    let vc = UIHostingController(rootView: ContentView()
                        .environmentObject(model)
                        .environmentObject(store))
                    controller.present(vc, animated: true, completion: nil)
                    result("limitApp invoked")
                    
                case "getLimitedApps":
                    if #available(iOS 16.0, *) {
                        let limitedApps = self.decodeLimitedApps()
                        NSLog("Debug: Returning limited apps: \(limitedApps)")
                        result(limitedApps)
                    } else {
                        NSLog("Debug: iOS < 16.0, cannot get limited apps.")
                        result(["No limited apps or iOS < 16.0"])
                    }
                    
                default:
                    NSLog("Debug: Unhandled method call: \(call.method)")
                    result(FlutterMethodNotImplemented)
                }
            }
        }
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    @available(iOS 16.0, *)
func decodeLimitedApps() -> [String] {
    guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else {
        NSLog("Debug: No app group defaults available.")
        return ["No limited apps found."]
    }
    
    let decoder = JSONDecoder()
    var limitedApps: [String] = []
    
    NSLog("Debug: Attempting to read limited apps from UserDefaults.")
    if let appBundleIDsData = defaults.data(forKey: Constants.limitedApplicationBundleIdentifiersKey) {
        NSLog("Debug: Found app bundle IDs data: \(appBundleIDsData.count) bytes")
        
        // Decode the data as [String]
        do {
            let bundleIDs = try decoder.decode([String].self, from: appBundleIDsData)
            if !bundleIDs.isEmpty {
                NSLog("Debug: Decoded bundle IDs: \(bundleIDs)")
                limitedApps = bundleIDs
                for bundleID in bundleIDs {
                    NSLog("Bundle Identifier: \(bundleID)")
                }
            } else {
                NSLog("Debug: Decoded bundle IDs array is empty.")
            }
        } catch {
            NSLog("Debug: Failed to decode bundle IDs: \(error.localizedDescription)")
            // Optionally, log the raw data as a string for inspection
            if let rawString = String(data: appBundleIDsData, encoding: .utf8) {
                NSLog("Debug: Raw appBundleIDsData as String: \(rawString)")
            }
        }
    } else {
        NSLog("Debug: No app bundle IDs data found in UserDefaults.")
    }
    
    if limitedApps.isEmpty {
        return ["No limited apps found!"]
    }
    
    return limitedApps
}




func storeLimitedApps(bundleIDs: [String], categoryIDs: [String]) {
    guard let defaults = UserDefaults(suiteName: Constants.appGroupID) else {
        NSLog("Debug: Failed to access UserDefaults with App Group ID.")
        return
    }

    let encoder = JSONEncoder()

    do {
        let appData = try encoder.encode(bundleIDs)
        defaults.set(appData, forKey: Constants.limitedApplicationBundleIdentifiersKey)
        NSLog("Debug: Stored \(bundleIDs.count) application bundle identifiers: \(bundleIDs)")
    } catch {
        NSLog("Debug: Failed to encode application bundle identifiers: \(error.localizedDescription)")
    }

    // Similarly handle categoryIDs if necessary
    do {
        let catData = try encoder.encode(categoryIDs)
        defaults.set(catData, forKey: Constants.limitedCategoryIdentifiersKey)
        NSLog("Debug: Stored \(categoryIDs.count) category identifiers.")
    } catch {
        NSLog("Debug: Failed to encode category identifiers: \(error.localizedDescription)")
    }

    defaults.synchronize()
}



}


ios swift flutter
1个回答
0
投票

您无法获取应用程序的名称及其标识符。 这是设备活动框架特意设计的隐私保护功能。

该框架提供了用于管理应用程序选择的 UI 类。 您的应用程序可以访问的只是代表这些 UI 类提供的应用程序的不透明令牌。 您无法为这些操作构建自己的 UI。

© www.soinside.com 2019 - 2024. All rights reserved.