Get list of installed apps on iPhone - objective-c

Is there a way (some API) to get the list of installed apps on an iPhone device.
While searching for similar questions, I found some thing related to url registration, but I think there must be some API to do this, as I don't want to do any thing with the app, I just want the list.

No, apps are sandboxed and Apple-accepted APIs do not include anything that would let you do that.
You can, however, test whether a certain app is installed:
if the app is known to handle URLs of a certain type
by using [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"thisapp://foo"]
You can get a list of apps and URL schemes from here.

For jailbroken devices you can use next snipped of code:
-(void)appInstalledList
{
static NSString* const path = #"/private/var/mobile/Library/Caches/com.apple.mobile.installation.plist";
NSDictionary *cacheDict = nil;
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath: path isDirectory: &isDir] && !isDir)
{
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
NSDictionary *system = [cacheDict objectForKey: #"System"]; // First check all system (jailbroken) apps
for (NSString *key in system)
{
NSLog(#"%#",key);
}
NSDictionary *user = [cacheDict objectForKey: #"User"]; // Then all the user (App Store /var/mobile/Applications) apps
for (NSString *key in user)
{
NSLog(#"%#",key);
}
return;
}
NSLog(#"can not find installed app plist");
}

for non jailbroken device, we can use third party framework which is called "ihaspp", also its free and apple accepted. Also they given good documentation how to integrate and how to use. May be this would be helpful to you. Good luck!!
https://github.com/danielamitay/iHasApp

You could do this by using the following:
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector = NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSMutableArray *Allapps = [workspace performSelector:selectorALL];
NSLog(#"apps: %#", Allapps);
And then by accessing each element and splitting it you can get your app name, and even the Bundle Identifier, too.

Well, not sure if this was available back when the last answer was given or not (Prior to iOS 6)
Also this one is time intensive, yet simple:
Go into settings > Gen. >usage. The first category under usage at least right now is Storage.
It will show a partial list of apps. At the bottom of this partial list is a button that says "show all apps".
Tap that and you'll have to go through screen by screen, and take screenshots (Quick lock button and home button takes a screenshot).
I'm doing this now and I have hundreds of apps on my iPhone. So it's going to take me a while. But at least at the end of the process I'll have Images of all my apps.

Related

macOS accessibility API on WebKit applications with AXTextMarker

I need to access data from webkit applications such as Safari, Mail and maybe others. I can see in the Accessibility Inspector there is :AXTextMarker and AXTextMarkerForRange.
I tried the usual way to get this info :
AXUIElementRef systemWideElement = AXUIElementCreateSystemWide(); //creating system wide element
AXUIElementRef focussedElement = NULL;
AXError error = AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute, (CFTypeRef *)&focussedElement); //Copy the focused
if (error != kAXErrorSuccess){
NSLog(#"Could not get focused element");
}else{
AXValueRef marker = NULL;
AXError getTextValueError = AXUIElementCopyAttributeValue(focussedElement, kAXMarkerUIElementsAttribute , (CFTypeRef *)&marker);
}
kAXMarkerUIElementsAttribute is the only thing I can see with Marker but everything is empty each time.
I guess for security reasons, I cannot access them? Is there any way possible. I am developing an app for people with difficulties reading and it could really help.
Thanks
Tips and tricks:
Ask focussedElement for its supported attributes. Use the functions:
AXError AXUIElementCopyAttributeNames(AXUIElementRef element, CFArrayRef _Nullable *names);
Returns a list of all the attributes supported by the specified accessibility object.
and
AXError AXUIElementCopyParameterizedAttributeNames(AXUIElementRef element, CFArrayRef _Nullable *names);
Returns a list of all the parameterized attributes supported by the specified accessibility object.
Most undocumented attributes are self explanatory.
For example get the selected text as attributed string:
CFTypeRef markerRange = NULL;
AXError error = AXUIElementCopyAttributeValue(focussedElement, (CFStringRef)#"AXSelectedTextMarkerRange", &markerRange);
CFTypeRef result = NULL;
error = AXUIElementCopyParameterizedAttributeValue(focussedElement, (CFStringRef)#"AXAttributedStringForTextMarkerRange", markerRange, &result);
Try this, I haven't tested this but I found some research on the API and I think this may solve your problem.
It seems for the accessibility API to fully work the application needs to be trusted or basically have root access. Is the application running in root?
I found some apple script from a past project. seemed relevant
NSDictionary *errorInfo = [NSDictionary new];
NSString *script = [NSString stringWithFormat:#"do shell script \"%# %#\" with administrator privileges", #"/usr/sbin/lsof",#"-c filecoord"];
NSLog(#"Running... %#",script);
NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];
This apple script isn't excatily what you need but its a start. I'm not sure how to fully request root access to an application from inside the application its self,
But for testing, you can always just run your application as root and continue your debugging
Please let me know if this helped you in any way or not
Accessibility apps need to be added in System Preferences in the following section:
System Preferences -> Security & Privacy -> Privacy tab -> Accessibility (on the left)
Note that during development you need to include Xcode in this list if you are developing/running within Xcode.
I didn't test out your exact code, but I tested some comparable swift code and that worked fine provided Xcode was added in that spot in system preferences.

Cocoa: Can I prevent duplicated launching of one same application?

For example, a user puts my application on his/her desktop. Then he/she copied (instead of moving) it to the /Application folder.
If the one at ~/Desktop has been launched, how can I prevent duplicated launching of the one at ~/Application? Any simple way?
Or if the user launches the one in /Application, I can detect the pre-launched app, and then switch to that immediately?
How about getting a list of all running applications and quitting immediately if your app detects there's another instance already running?
You can get a list of all active apps via NSWorkspace and runningApplications:
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
NSArray *allRunningApps = [ws runningApplications];
It'll return an array of NSRunningApplication instances so all you'd have to do is check the list for e.g. your app's ID via the bundleIdentifier method.
cacau's answer had given me inspiration to achieve my goal. Here are my codes (ARC):
- (BOOL)checkAppDuplicateAndBringToFrontWithBundle:(NSBundle *)bundle
{
NSRunningApplication *app;
NSArray *appArray;
NSUInteger tmp;
pid_t selfPid;
BOOL ret = NO;
selfPid = [[NSRunningApplication currentApplication] processIdentifier];
appArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:[bundle bundleIdentifier]];
for (tmp = 0; tmp < [appArray count]; tmp++)
{
app = [appArray objectAtIndex:tmp];
if ([app processIdentifier] == selfPid)
{
/* do nothing */
}
else
{
[[NSWorkspace sharedWorkspace] launchApplication:[[app bundleURL] path]];
ret = YES;
}
}
return ret;
}
It checks app duplicate by bundle identifier. As bundle duplicated, it brings all other applications to front and returns YES. As receiving YES, application can terminate itself.
However, as cacau has given me significant help, I gave him the reputation point of this question. Thank you!
Would it not be simplest to actually just create a file inside NSTempDirectory, check for it and if it is there, you quit, or perhaps offer a way to delete the file ?
This would work for both running in and out of sandbox.

Launching Facetime from your app?

I am seeing that you can launch FaceTime from your app via
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"facetime://tel-number"]];
I am also reading that since there is no officially public FaceTime API apple will reject you.
Does anyone know if this rejection talk is true? PAIR has this feature and they have never been rejected.
This is now documented and legal:
https://developer.apple.com/library/ios/featuredarticles/iPhoneURLScheme_Reference/FacetimeLinks/FacetimeLinks.html#//apple_ref/doc/uid/TP40007899-CH2-SW1
My app got rejected for using FaceTime url. This is the part of response i got from Apple in resolution center.
We found the following non-public API/s in your app: Specifically,
your app uses the FaceTime URL scheme, which is undocumented.
If you have defined methods in your source code with the same names as
the above-mentioned APIs, we suggest altering your method names so
that they no longer collide with Apple's private APIs to avoid your
application being flagged in future submissions.
It was an update of a previous release. The first version got accepted without any problem. Now the update has been rejected due to the above mentioned reason. Seems i have to publish the app without the FaceTime thingy now.
Edit:
Its now legal to use FaceTime url in third party apps.
As a general rule, if you use undocumented API calls and apple catches you, they will reject your application. The reason is because they could change the API call that you are using in new IOS updates and thus would cause your application to crash or not work properly. You can try and submit using the undocumented API and hope that apple lets it through but as i said, you run the risk of Apple changing this api call or removing it completely from the OS in the future.
I don't see any reason this would be rejected, especially if there's already an app that uses this functionality. The App Store Review Guidelines are the best way to determine if your app will be rejected, and I don't see anything in there that applies to you situation.
Of course, Apple can do whatever they want, so the only way to be absolutely sure it will be accepted is to submit it, but I highly doubt you will have a problem.
It is official that you can use Native app URL strings for FaceTime video calls:
facetime:// 14085551234
facetime://user#example.com
Please refer to the link: https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/FacetimeLinks/FacetimeLinks.html
Though this feature is supported on all devices, you have to change the code a little bit for iOS 10.0 and above as openURL(_:) is deprecated.
https://developer.apple.com/documentation/uikit/uiapplication/1622961-openurl?language=objc
Please refer code below for the current and fallback mechanism, so this way it will not get rejected by Appstore.
-(void) callFaceTime : (NSString *) contactNumber
{
NSURL *URL = [NSURL URLWithString:[NSString
stringWithFormat:#"facetime://%#", contactNumber]];
if (#available(iOS 10.0, *)) {
[[UIApplication sharedApplication] openURL:URL options:#{}
completionHandler:^(BOOL success)
{
if (success)
{
NSLog(#"inside success");
}
else
{
NSLog(#"error");
}
}];
}
else {
// Fallback on earlier versions
NSString *faceTimeUrlScheme = [#"facetime://"
stringByAppendingString:contactNumber];
NSURL *facetimeURL = [NSURL URLWithString:faceTimeUrlScheme];
// Facetime is available or not
if ([[UIApplication sharedApplication] canOpenURL:facetimeURL])
{
[[UIApplication sharedApplication] openURL:facetimeURL];
}
else
{
// Facetime not available
NSLog(#"Facetime not available");
}
}
}
in contactNumber either pass phone number or appleid.
NSString *phoneNumber = #"9999999999";
NSString *appleId = #"abc#gmail.com";
[self callFaceTime:appleId];
objective-c ios

Global events, the Mac App Store, and the sandbox

I'm working on an app where using global key-down events will be a requirement for its operation. Additionally, I plan on distributing this strictly via the App Store. (It's a Mac app, not iOS.) I've gotten an example of listening for the global events working via addGlobalMonitorForEventsMatchingMask, but with caveats.
Note: I am making the choice to use the modern API's and not rely on the earlier Carbon hotkey methods. In the event that they are deprecated eventually, I don't want to have to figure this problem out later.
The principle issue is that the app has to be trusted in order for global events to be detected. Otherwise, accessibility has to be enabled for all apps. When I enable accessibility, events are detected successfully. This requirement is documented here, https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html.
I would prefer that for my users, they will not have to enable accessibility. From other research I've done, you can get an application to be trusted by calling AXMakeProcessTrusted, then restarting the application.
In the code that I'm using, I do not get an authentication prompt. The app will restart, but is still not trusted (likely because I don't get an authentication prompt). Here's my code for this part:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
if (!AXAPIEnabled() && !AXIsProcessTrusted()) {
NSString *appPath = [[NSBundle mainBundle] bundlePath];
AXError error = AXMakeProcessTrusted( (CFStringRef)CFBridgingRetain(appPath) );
[self restartApp];
}
}
- (void)restartApp{
NSTask *task = [[NSTask alloc] init];
NSMutableArray *args = [NSMutableArray array];
[args addObject:#"-c"];
[args addObject:[NSString stringWithFormat:#"sleep %d; open \"%#\"", 3, [[NSBundle mainBundle] bundlePath]]];
[task setLaunchPath:#"/bin/sh"];
[task setArguments:args];
[task launch];
[NSApp terminate:nil];
}
Further, I've looked at the documentation for Authorization Service Tasks here https://developer.apple.com/library/archive/documentation/Security/Conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG.
The first thing that worries me that pops out is this info box, "Important The authorization services API is not supported within an app sandbox because it allows privilege escalation."
If this API is required to get the authentication prompt before restarting the app, it seems that I may not be able to get global events without the accessibility feature enabled.
In summary, my specific questions are:
Is there an error in my sample code about how to get the
authentication prompt to appear?
In order to get the authentication prompt to appear, am I required
to use the Authorization Services API?
Is it possible, or not possible, to have a sandboxed app that has
access to global events?
First of all, there is no way you can automatically allow an app to use accessibility API which would work in a sandbox environment and thus in app store. The recommended way is to simply guide users so they can easily enable it themselves. The new API call AXIsProcessTrustedWithOptions is exactly for that:
NSDictionary *options = #{(id) kAXTrustedCheckOptionPrompt : #YES};
AXIsProcessTrustedWithOptions((CFDictionaryRef) options);
Now, to your first and second question (just for the sake of completeness - again it won't work in sandbox):
The idea behind AXMakeProcessTrusted was that you actually create a new auxiliary application that you run as root from the main application. This utility then calls AXMakeProcessTrusted passing in the executable of the main application. Finally you have to restart the main app. The API call has been deprecated in OSX 10.9.
To spawn a new process as a root you have to use launchd using SMJobSubmit. This will prompt a user with an authentication prompt saying that an application is trying to install a helper tool and whether it should be allowed. Concretely:
+ (BOOL)makeTrustedWithError:(NSError **)error {
NSString *label = FMTStr(#"%#.%#", kShiftItAppBundleId, #"mktrusted");
NSString *command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:#"mktrusted"];
AuthorizationItem authItem = {kSMRightModifySystemDaemons, 0, NULL, 0};
AuthorizationRights authRights = {1, &authItem};
AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
AuthorizationRef auth;
if (AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &auth) == errAuthorizationSuccess) {
// this is actually important - if from any reason the job was not removed, it won't relaunch
// to check for the running jobs use: sudo launchctl list
// the sudo is important since this job runs under root
SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
// this is actually the launchd plist for a new process
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist
NSDictionary *plist = #{
#"Label" : label,
#"RunAtLoad" : #YES,
#"ProgramArguments" : #[command],
#"Debug" : #YES
};
BOOL ret;
if (SMJobSubmit(kSMDomainSystemLaunchd, (CFDictionaryRef) plist, auth, (CFErrorRef *) error)) {
FMTLogDebug(#"Executed %#", command);
ret = YES;
} else {
FMTLogError(#"Failed to execute %# as priviledged process: %#", command, *error);
ret = NO;
}
// From whatever reason this did not work very well
// seems like it removed the job before it was executed
// SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
AuthorizationFree(auth, 0);
return ret;
} else {
FMTLogError(#"Unable to create authorization object");
return NO;
}
}
As for the restarting, this is usually done also using an external utility to which waits for a main application to finish and starts it again (by using PID). If you use sparkle framework you can reuse the existing one:
+ (void) relaunch {
NSString *relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:#"relaunch" ofType:#""];
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *pid = FMTStr(#"%d", [[NSProcessInfo processInfo] processIdentifier]);
[NSTask launchedTaskWithLaunchPath:relaunch arguments:#[path, pid]];
[NSApp terminate:self];
}
Another option is to hack the /Library/Application Support/com.apple.TCC/TCC.db sqlite database add the permissions manually using an auxiliary helper:
NSString *sqlite = #"/usr/bin/sqlite3";
NSString *sql = FMTStr(#"INSERT or REPLACE INTO access values ('kTCCServiceAccessibility', '%#', 1, 1, 1, NULL);", MY_BUNDLE_ID);
NSArray *args = #[#"/Library/Application Support/com.apple.TCC/TCC.db", sql];
NSTask *task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
[task waitUntilExit];
This however will disqualify the app from being app store. More over it is really just a hack and the db / schema can change any time. Some applications (e.g. Divvy.app used to do this) used this hack within the application installer post install script. This way prevents the dialog telling that an app is requesting to install an auxiliary tool.
Basically, MAS restrictions will require you to the route of having tge user turning on AX for all.
I found a potential solution on GitHub.
https://github.com/K8TIY/CW-Station
It has an auxiliary application which would be run at root to request access for the main application. It is a little outdated and is using some functions which have been deprecated so I am working on modernizing it. It looks like a good starting point.

Play specific title in iTunes via ScriptingBridge

I'm trying to write an application that interacts with iTunes via ScriptingBridge. I works well so far, but the options of this method seem to be very limited.
I want to play song with a given name, but it looks like there's no way to do this. I haven't found anything similar in iTunes.h…
In AppleScript it's just three lines of code:
tell application "iTunes"
play (some file track whose name is "Yesterday")
end tell
And then iTunes starts to play a classic Beatles song.
Is there any was I can do this with ScriptingBridge or do I have to run this AppleScript from my app?
It's not as simple as the AppleScript version, but it's certainly possible.
Method one
Get a pointer to the iTunes library:
iTunesApplication *iTunesApp = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
SBElementArray *iTunesSources = [iTunesApp sources];
iTunesSource *library;
for (iTunesSource *thisSource in iTunesSources) {
if ([thisSource kind] == iTunesESrcLibrary) {
library = thisSource;
break;
}
}
Get an array containing all the audio file tracks in the library:
SBElementArray *libraryPlaylists = [library libraryPlaylists];
iTunesLibraryPlaylist *libraryPlaylist = [libraryPlaylists objectAtIndex:0];
SBElementArray *musicTracks = [self.libraryPlaylist fileTracks];
Then filter the array to find tracks with the title you're looking for.
NSArray *tracksWithOurTitle = [musicTracks filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"name", #"Yesterday"]];
// Remember, there might be several tracks with that title; you need to figure out how to find the one you want.
iTunesTrack *rightTrack = [tracksWithOurTitle objectAtIndex:0];
[rightTrack playOnce:YES];
Method two
Get a pointer to the iTunes library as above. Then use the Scripting Bridge searchFor: only: method:
SBElementArray *tracksWithOurTitle = [library searchFor:#"Yesterday" only:kSrS];
// This returns every song whose title *contains* "Yesterday" ...
// You'll need a better way to than this to pick the one you want.
iTunesTrack *rightTrack = [tracksWithOurTitle objectAtIndex:0];
[rightTrack playOnce:YES];
Caveat to method two: The iTunes.h file incorrectly claims that the searchFor: only: method returns an iTunesTrack*, when in fact (for obvious reasons) it returns an SBElementArray*. You can edit the header file to get rid of the resulting compiler warning.