1 #import <Foundation/Foundation.h>
2 #import "LSApplicationProxy+AltList.h"
4 @implementation LSApplicationProxy (AltList)
5 // the tag " hidden " is also valid, so we need to check if any strings contain "hidden" instead
6 BOOL tagArrayContainsTag(NSArray* tagArr, NSString* tag)
8 if(!tagArr || !tag) return NO;
10 __block BOOL found = NO;
12 [tagArr enumerateObjectsUsingBlock:^(NSString* tagToCheck, NSUInteger idx, BOOL* stop)
14 if(![tagToCheck isKindOfClass:[NSString class]])
19 if([tagToCheck rangeOfString:tag options:0].location != NSNotFound)
29 // always returns NO on iOS 7
33 NSArray* recordAppTags;
36 BOOL launchProhibited = NO;
38 if([self respondsToSelector:@selector(correspondingApplicationRecord)])
40 // On iOS 14, self.appTags is always empty but the application record still has the correct ones
41 LSApplicationRecord* record = [self correspondingApplicationRecord];
42 recordAppTags = record.appTags;
43 launchProhibited = record.launchProhibited;
45 if([self respondsToSelector:@selector(appTags)])
47 appTags = self.appTags;
49 if(!launchProhibited && [self respondsToSelector:@selector(isLaunchProhibited)])
51 launchProhibited = self.launchProhibited;
54 NSURL* bundleURL = self.bundleURL;
55 if(bundleURL && [bundleURL checkResourceIsReachableAndReturnError:nil])
57 NSBundle* bundle = [NSBundle bundleWithURL:bundleURL];
58 sbAppTags = [bundle objectForInfoDictionaryKey:@"SBAppTags"];
61 BOOL isWebApplication = ([self.bundleIdentifier rangeOfString:@"com.apple.webapp" options:NSCaseInsensitiveSearch].location != NSNotFound);
62 return tagArrayContainsTag(appTags, @"hidden") || tagArrayContainsTag(recordAppTags, @"hidden") || tagArrayContainsTag(sbAppTags, @"hidden") || isWebApplication || launchProhibited;
65 // Getting the display name is slow (up to 2ms) because it uses an IPC call
66 // this stacks up if you do it for every single application
67 // This method provides a faster way (around 0.5ms) to get the display name
68 // This reduces the overall time needed to sort the applications from ~230 to ~120ms on my test device
69 - (NSString*)atl_fastDisplayName
71 NSString* cachedDisplayName = [self valueForKey:@"_localizedName"];
72 if(cachedDisplayName && ![cachedDisplayName isEqualToString:@""])
74 return cachedDisplayName;
77 NSString* localizedName;
79 NSURL* bundleURL = self.bundleURL;
80 if(!bundleURL || ![bundleURL checkResourceIsReachableAndReturnError:nil])
82 localizedName = self.localizedName;
86 NSBundle* bundle = [NSBundle bundleWithURL:bundleURL];
88 localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
89 if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil;
90 if(!localizedName || [localizedName isEqualToString:@""])
92 localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleName"];
93 if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil;
94 if(!localizedName || [localizedName isEqualToString:@""])
96 localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleExecutable"];
97 if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil;
98 if(!localizedName || [localizedName isEqualToString:@""])
100 //last possible fallback: use slow IPC call
101 localizedName = self.localizedName;
107 [self setValue:localizedName forKey:@"_localizedName"];
108 return localizedName;
111 - (NSString*)atl_nameToDisplay
113 NSString* localizedName = [self atl_fastDisplayName];
115 if([self.bundleIdentifier rangeOfString:@"carplay" options:NSCaseInsensitiveSearch].location != NSNotFound)
117 if([localizedName rangeOfString:@"carplay" options:NSCaseInsensitiveSearch range:NSMakeRange(0, localizedName.length) locale:[NSLocale currentLocale]].location == NSNotFound)
119 return [localizedName stringByAppendingString:@" (CarPlay)"];
123 return localizedName;