#import #import "LSApplicationProxy+AltList.h" @implementation LSApplicationProxy (AltList) // the tag " hidden " is also valid, so we need to check if any strings contain "hidden" instead BOOL tagArrayContainsTag(NSArray* tagArr, NSString* tag) { if(!tagArr || !tag) return NO; __block BOOL found = NO; [tagArr enumerateObjectsUsingBlock:^(NSString* tagToCheck, NSUInteger idx, BOOL* stop) { if(![tagToCheck isKindOfClass:[NSString class]]) { return; } if([tagToCheck rangeOfString:tag options:0].location != NSNotFound) { found = YES; *stop = YES; } }]; return found; } // always returns NO on iOS 7 - (BOOL)atl_isHidden { NSArray* appTags; NSArray* recordAppTags; NSArray* sbAppTags; BOOL launchProhibited = NO; if([self respondsToSelector:@selector(correspondingApplicationRecord)]) { // On iOS 14, self.appTags is always empty but the application record still has the correct ones LSApplicationRecord* record = [self correspondingApplicationRecord]; recordAppTags = record.appTags; launchProhibited = record.launchProhibited; } if([self respondsToSelector:@selector(appTags)]) { appTags = self.appTags; } if(!launchProhibited && [self respondsToSelector:@selector(isLaunchProhibited)]) { launchProhibited = self.launchProhibited; } NSURL* bundleURL = self.bundleURL; if(bundleURL && [bundleURL checkResourceIsReachableAndReturnError:nil]) { NSBundle* bundle = [NSBundle bundleWithURL:bundleURL]; sbAppTags = [bundle objectForInfoDictionaryKey:@"SBAppTags"]; } BOOL isWebApplication = ([self.bundleIdentifier rangeOfString:@"com.apple.webapp" options:NSCaseInsensitiveSearch].location != NSNotFound); return tagArrayContainsTag(appTags, @"hidden") || tagArrayContainsTag(recordAppTags, @"hidden") || tagArrayContainsTag(sbAppTags, @"hidden") || isWebApplication || launchProhibited; } // Getting the display name is slow (up to 2ms) because it uses an IPC call // this stacks up if you do it for every single application // This method provides a faster way (around 0.5ms) to get the display name // This reduces the overall time needed to sort the applications from ~230 to ~120ms on my test device - (NSString*)atl_fastDisplayName { NSString* cachedDisplayName = [self valueForKey:@"_localizedName"]; if(cachedDisplayName && ![cachedDisplayName isEqualToString:@""]) { return cachedDisplayName; } NSString* localizedName; NSURL* bundleURL = self.bundleURL; if(!bundleURL || ![bundleURL checkResourceIsReachableAndReturnError:nil]) { localizedName = self.localizedName; } else { NSBundle* bundle = [NSBundle bundleWithURL:bundleURL]; localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; if(!localizedName || [localizedName isEqualToString:@""]) { localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; if(!localizedName || [localizedName isEqualToString:@""]) { localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleExecutable"]; if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; if(!localizedName || [localizedName isEqualToString:@""]) { //last possible fallback: use slow IPC call localizedName = self.localizedName; } } } } [self setValue:localizedName forKey:@"_localizedName"]; return localizedName; } - (NSString*)atl_nameToDisplay { NSString* localizedName = [self atl_fastDisplayName]; if([self.bundleIdentifier rangeOfString:@"carplay" options:NSCaseInsensitiveSearch].location != NSNotFound) { if([localizedName rangeOfString:@"carplay" options:NSCaseInsensitiveSearch range:NSMakeRange(0, localizedName.length) locale:[NSLocale currentLocale]].location == NSNotFound) { return [localizedName stringByAppendingString:@" (CarPlay)"]; } } return localizedName; } @end