From e3ea200ad5af3a635b4fcbe1072647231803f3ba Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 17 Dec 2019 19:17:45 -0800 Subject: [PATCH] Uses UTIs to determine whether apps are installed or not AltStore now inserts an app-specific UTI when resigning apps, and it periodically checks whether that app has been deleted by checking whether UTTypeCopyDeclaration returns nil for the same app-specific UTI. --- AltKit/Bundle+AltStore.swift | 1 + AltStore/Managing Apps/AppManager.swift | 8 ++++++-- AltStore/Model/InstalledApp.swift | 10 ++++++++++ AltStore/Operations/ResignAppOperation.swift | 11 +++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/AltKit/Bundle+AltStore.swift b/AltKit/Bundle+AltStore.swift index 1930b9ce..90f065b7 100644 --- a/AltKit/Bundle+AltStore.swift +++ b/AltKit/Bundle+AltStore.swift @@ -18,6 +18,7 @@ public extension Bundle public static let appGroups = "ALTAppGroups" public static let urlTypes = "CFBundleURLTypes" + public static let exportedUTIs = "UTExportedTypeDeclarations" } } diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 45d85dc3..3a41bcc8 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -9,6 +9,7 @@ import Foundation import UIKit import UserNotifications +import MobileCoreServices import AltSign import AltKit @@ -55,16 +56,19 @@ extension AppManager do { let installedApps = try context.fetch(fetchRequest) - for app in installedApps where app.storeApp != nil + for app in installedApps { + let uti = UTTypeCopyDeclaration(app.installedAppUTI as CFString)?.takeRetainedValue() as NSDictionary? + if app.bundleIdentifier == StoreApp.altstoreAppID { self.scheduleExpirationWarningLocalNotification(for: app) } else { - if !UIApplication.shared.canOpenURL(app.openAppURL) + if uti == nil { + // This UTI is not declared by any apps, which means this app has been deleted by the user. context.delete(app) } } diff --git a/AltStore/Model/InstalledApp.swift b/AltStore/Model/InstalledApp.swift index 4a7854e2..4acea713 100644 --- a/AltStore/Model/InstalledApp.swift +++ b/AltStore/Model/InstalledApp.swift @@ -187,6 +187,12 @@ extension InstalledApp return directoryURL } + class func installedAppUTI(forBundleIdentifier bundleIdentifier: String) -> String + { + let installedAppUTI = "io.altstore.Installed." + bundleIdentifier + return installedAppUTI + } + var directoryURL: URL { return InstalledApp.directoryURL(for: self) } @@ -198,4 +204,8 @@ extension InstalledApp var refreshedIPAURL: URL { return InstalledApp.refreshedIPAURL(for: self) } + + var installedAppUTI: String { + return InstalledApp.installedAppUTI(forBundleIdentifier: self.resignedBundleIdentifier) + } } diff --git a/AltStore/Operations/ResignAppOperation.swift b/AltStore/Operations/ResignAppOperation.swift index 5671ce97..bb14a4c4 100644 --- a/AltStore/Operations/ResignAppOperation.swift +++ b/AltStore/Operations/ResignAppOperation.swift @@ -372,6 +372,17 @@ private extension ResignAppOperation infoDictionary[Bundle.Info.appGroups] = appGroups } + // Add app-specific exported UTI so we can check later if this app (extension) is installed or not. + let installedAppUTI = ["UTTypeConformsTo": [], + "UTTypeDescription": "AltStore Installed App", + "UTTypeIconFiles": [], + "UTTypeIdentifier": InstalledApp.installedAppUTI(forBundleIdentifier: profile.bundleIdentifier), + "UTTypeTagSpecification": [:]] as [String : Any] + + var exportedUTIs = infoDictionary[Bundle.Info.exportedUTIs] as? [[String: Any]] ?? [] + exportedUTIs.append(installedAppUTI) + infoDictionary[Bundle.Info.exportedUTIs] = exportedUTIs + try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL) }