Files
keyboard/Shared/KBKeyboardPermissionManager.m
2025-11-03 21:04:39 +08:00

95 lines
3.6 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBKeyboardPermissionManager.m
//
#import "KBKeyboardPermissionManager.h"
#import <Security/Security.h>
#import "KBConfig.h"
// Keychain 存储:记录上次扩展上报的“完全访问”状态
static NSString * const kKBPermService = @"com.loveKey.nyx.perm";
static NSString * const kKBPermAccount = @"full_access"; // 保存一个字节/数字0/1/2
@implementation KBKeyboardPermissionManager
+ (instancetype)shared {
static KBKeyboardPermissionManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBKeyboardPermissionManager new]; });
return m;
}
#pragma mark - App side
- (BOOL)isKeyboardEnabled {
// 与 AppDelegate 中同思路:遍历 activeInputModes匹配自家扩展 bundle id
for (UITextInputMode *mode in [UITextInputMode activeInputModes]) {
NSString *identifier = nil;
@try { identifier = [mode valueForKey:@"identifier"]; } @catch (__unused NSException *e) { identifier = nil; }
if ([identifier isKindOfClass:NSString.class] && [identifier rangeOfString:KB_KEYBOARD_EXTENSION_BUNDLE_ID].location != NSNotFound) {
return YES;
}
}
return NO;
}
- (KBFARecord)lastKnownFullAccess {
NSData *data = [self keychainRead];
if (data.length == 0) return KBFARecordUnknown;
uint8_t v = 0; [data getBytes:&v length:1];
if (v > KBFARecordGranted) v = KBFARecordUnknown;
return (KBFARecord)v;
}
- (void)presentPermissionIfNeededFrom:(UIViewController *)presenting {
BOOL enabled = [self isKeyboardEnabled];
KBFARecord fa = [self lastKnownFullAccess];
// 策略:
// - 未启用键盘:一定引导;
// - 已启用键盘仅当明确知道“完全访问被拒绝”才引导Unknown 不打扰(等待扩展上报)。
BOOL needGuide = (!enabled) || (enabled && fa == KBFARecordDenied);
if (!needGuide || !presenting) return;
Class cls = NSClassFromString(@"KBPermissionViewController");
if (!cls) return; // 主 App 才存在该类
UIViewController *guide = [cls new];
guide.modalPresentationStyle = UIModalPresentationFullScreen;
[presenting presentViewController:guide animated:YES completion:nil];
}
#pragma mark - Extension side
- (void)reportFullAccessFromExtension:(BOOL)granted {
uint8_t v = granted ? KBFARecordGranted : KBFARecordDenied;
NSData *data = [NSData dataWithBytes:&v length:1];
[self keychainWrite:data];
}
#pragma mark - Keychain shared blob
- (NSMutableDictionary *)baseKCQuery {
NSMutableDictionary *q = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKBPermService,
(__bridge id)kSecAttrAccount: kKBPermAccount } mutableCopy];
q[(__bridge id)kSecAttrAccessGroup] = KB_KEYCHAIN_ACCESS_GROUP;
return q;
}
- (BOOL)keychainWrite:(NSData *)data {
NSMutableDictionary *query = [self baseKCQuery];
SecItemDelete((__bridge CFDictionaryRef)query);
query[(__bridge id)kSecValueData] = data ?: [NSData data];
query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
OSStatus st = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
return (st == errSecSuccess);
}
- (NSData *)keychainRead {
NSMutableDictionary *query = [self baseKCQuery];
query[(__bridge id)kSecReturnData] = @YES;
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
CFTypeRef dataRef = NULL; OSStatus st = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
if (st != errSecSuccess || !dataRef) return nil;
return (__bridge_transfer NSData *)dataRef;
}
@end