我正在尝试使用家庭控制模块来限制应用程序的屏幕时间。限制器工作正常,但我希望能够看到我的 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()
}
}
您无法获取应用程序的名称及其标识符。 这是设备活动框架特意设计的隐私保护功能。
该框架提供了用于管理应用程序选择的 UI 类。 您的应用程序可以访问的只是代表这些 UI 类提供的应用程序的不透明令牌。 您无法为这些操作构建自己的 UI。