Fix iOS 26 app manager blank state
This commit is contained in:
1 parent
53498ed08f
commit
168a89cb8a
4 files changed
+361
-15
No files matched your search
+113
-3
@@ -698,7 +698,7 @@ static void WQInstallAppLinks(NSArray<NSDictionary<NSString *, NSString *> *> *a
|
||||
(unsigned long)uuidLinked, (unsigned long)apps.count);
|
||||
}
|
||||
|
||||
static NSString *WQFallbackDisplayName(NSString *bundleID)
|
||||
NSString *WQFallbackDisplayName(NSString *bundleID)
|
||||
{
|
||||
static NSDictionary<NSString *, NSString *> *names;
|
||||
static dispatch_once_t onceToken;
|
||||
@@ -968,6 +968,101 @@ static void WQDiscoverPerContainerFamilies(void)
|
||||
}
|
||||
}
|
||||
|
||||
static NSDictionary<NSString *, NSDictionary *> *WQMobileInstallationApps(void)
|
||||
{
|
||||
static NSDictionary *apps;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
void *handle = dlopen(
|
||||
"/System/Library/PrivateFrameworks/MobileInstallation.framework/MobileInstallation",
|
||||
RTLD_LAZY);
|
||||
if (!handle) {
|
||||
NSLog(@"[WQ] MobileInstallation framework unavailable: %s", dlerror());
|
||||
return;
|
||||
}
|
||||
NSDictionary *(*lookup)(NSDictionary *, NSError **) =
|
||||
(NSDictionary *(*)(NSDictionary *, NSError **))dlsym(handle,
|
||||
"MobileInstallationLookup");
|
||||
if (lookup) {
|
||||
NSError *error = nil;
|
||||
id value = lookup(nil, &error);
|
||||
if ([value isKindOfClass:NSDictionary.class]) apps = [value copy];
|
||||
NSLog(@"[WQ] MobileInstallationLookup apps=%lu error=%@",
|
||||
(unsigned long)apps.count,
|
||||
error.localizedDescription ?: @"none");
|
||||
} else {
|
||||
NSLog(@"[WQ] MobileInstallationLookup symbol missing");
|
||||
}
|
||||
dlclose(handle);
|
||||
});
|
||||
return apps ?: @{};
|
||||
}
|
||||
|
||||
static void WQAppendMobileInstallationApps(NSMutableArray *result,
|
||||
NSMutableSet<NSString *> *known)
|
||||
{
|
||||
NSDictionary<NSString *, NSDictionary *> *apps = WQMobileInstallationApps();
|
||||
for (NSString *key in apps) {
|
||||
NSDictionary *entry = apps[key];
|
||||
if (![entry isKindOfClass:NSDictionary.class]) continue;
|
||||
NSString *bundleID = entry[@"CFBundleIdentifier"];
|
||||
if (![bundleID isKindOfClass:NSString.class] || !bundleID.length)
|
||||
bundleID = key;
|
||||
if (!bundleID.length || [known containsObject:bundleID]) continue;
|
||||
|
||||
id containerValue = entry[@"Container"];
|
||||
NSString *dataPath = [containerValue isKindOfClass:NSURL.class]
|
||||
? [(NSURL *)containerValue path] : containerValue;
|
||||
if (![dataPath isKindOfClass:NSString.class] || !dataPath.length) {
|
||||
NSString *error = nil;
|
||||
dataPath = MCMFilzaDataContainerPath(bundleID, &error);
|
||||
}
|
||||
id bundleValue = entry[@"Path"];
|
||||
NSString *bundlePath = [bundleValue isKindOfClass:NSURL.class]
|
||||
? [(NSURL *)bundleValue path] : bundleValue;
|
||||
if (![bundlePath isKindOfClass:NSString.class] || !bundlePath.length) {
|
||||
bundleValue = entry[@"BundlePath"];
|
||||
bundlePath = [bundleValue isKindOfClass:NSURL.class]
|
||||
? [(NSURL *)bundleValue path] : bundleValue;
|
||||
}
|
||||
if (![bundlePath isKindOfClass:NSString.class] || !bundlePath.length)
|
||||
bundlePath = WQBundlePathForIdentifier(bundleID);
|
||||
|
||||
NSMutableDictionary *info = [@{
|
||||
@"bundleID": bundleID,
|
||||
@"name": bundleID,
|
||||
@"version": @"1.0",
|
||||
} mutableCopy];
|
||||
if (dataPath.length) info[@"dataPath"] = dataPath;
|
||||
if (bundlePath.length) {
|
||||
info[@"bundlePath"] = bundlePath;
|
||||
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:
|
||||
[bundlePath stringByAppendingPathComponent:@"Info.plist"]];
|
||||
NSString *name = plist[@"CFBundleDisplayName"];
|
||||
if (!name.length) name = plist[@"CFBundleName"];
|
||||
if (name.length) info[@"name"] = name;
|
||||
NSString *version = plist[@"CFBundleShortVersionString"];
|
||||
if (!version.length) version = plist[@"CFBundleVersion"];
|
||||
if (version.length) info[@"version"] = version;
|
||||
}
|
||||
NSString *name = entry[@"CFBundleDisplayName"];
|
||||
if (!name.length) name = entry[@"CFBundleName"];
|
||||
if ([name isKindOfClass:NSString.class] && name.length)
|
||||
info[@"name"] = name;
|
||||
NSString *version = entry[@"CFBundleShortVersionString"];
|
||||
if (!version.length) version = entry[@"CFBundleVersion"];
|
||||
if ([version isKindOfClass:NSString.class] && version.length)
|
||||
info[@"version"] = version;
|
||||
if ([info[@"name"] isEqualToString:bundleID])
|
||||
info[@"name"] = WQFallbackDisplayName(bundleID);
|
||||
[result addObject:info];
|
||||
[known addObject:bundleID];
|
||||
}
|
||||
if (apps.count)
|
||||
NSLog(@"[WQ] MobileInstallation merged=%lu total=%lu",
|
||||
(unsigned long)apps.count, (unsigned long)result.count);
|
||||
}
|
||||
|
||||
static NSArray<NSDictionary<NSString *, NSString *> *> *WQBuildAppList(void)
|
||||
{
|
||||
NSString *root = @"/private/var/mobile/Containers/Data/Application";
|
||||
@@ -984,7 +1079,12 @@ static NSArray<NSDictionary<NSString *, NSString *> *> *WQBuildAppList(void)
|
||||
if (WQGrantContainer(container)) granted++;
|
||||
}
|
||||
WQPostStartupProgress(0.48, @"正在从数据目录识别应用…");
|
||||
NSArray *apps = WQAppInfoFromContainers(containers);
|
||||
NSArray *containerApps = WQAppInfoFromContainers(containers);
|
||||
NSMutableArray *apps = [containerApps mutableCopy];
|
||||
NSMutableSet<NSString *> *known = [NSMutableSet set];
|
||||
for (NSDictionary *info in containerApps)
|
||||
if (info[@"bundleID"]) [known addObject:info[@"bundleID"]];
|
||||
WQAppendMobileInstallationApps(apps, known);
|
||||
WQInstallAppLinks(apps);
|
||||
NSLog(@"[WQ] app discovery containers=%lu granted=%lu apps=%lu",
|
||||
(unsigned long)containers.count, (unsigned long)granted,
|
||||
@@ -1000,7 +1100,9 @@ NSArray<NSDictionary<NSString *, NSString *> *> *WQInstalledApplications(void)
|
||||
dispatch_sync(gAppsQueue, ^{
|
||||
NSTimeInterval age = gAppsCacheDate
|
||||
? -[gAppsCacheDate timeIntervalSinceNow] : 1e9;
|
||||
if (!gAppsCache || age > 120) {
|
||||
// Never cache an empty result: the first call can happen before the
|
||||
// worse_query grants and app-container scan have finished.
|
||||
if (!gAppsCache || gAppsCache.count == 0 || age > 120) {
|
||||
gAppsCache = WQBuildAppList();
|
||||
gAppsCacheDate = [NSDate date];
|
||||
}
|
||||
@@ -1133,6 +1235,14 @@ NSString *WQBundlePathForIdentifier(NSString *bundleID)
|
||||
NSDictionary *info = gBundleInfo[bundleID];
|
||||
if ([info[@"path"] isKindOfClass:NSString.class]) return info[@"path"];
|
||||
|
||||
NSDictionary *installation = WQMobileInstallationApps()[bundleID];
|
||||
if ([installation isKindOfClass:NSDictionary.class]) {
|
||||
NSString *path = installation[@"Path"];
|
||||
if (![path isKindOfClass:NSString.class] || !path.length)
|
||||
path = installation[@"BundlePath"];
|
||||
if ([path isKindOfClass:NSString.class] && path.length) return path;
|
||||
}
|
||||
|
||||
Class proxyClass = NSClassFromString(@"LSApplicationProxy");
|
||||
id proxy = [proxyClass respondsToSelector:
|
||||
NSSelectorFromString(@"applicationProxyForIdentifier:")]
|
||||
|
||||
Reference in new issue
Block a user