Detect when the NSE isn’t launching
This commit is contained in:
parent
a1c0cd1cd2
commit
d0c322fec9
@ -188,6 +188,7 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
|
||||
searchResultsController.viewDidAppear(animated)
|
||||
|
||||
showBadgeSheetIfNecessary()
|
||||
Task { await self.checkForFailedServiceExtensionLaunches() }
|
||||
|
||||
hasEverAppeared = true
|
||||
if viewState.multiSelectState.isActive {
|
||||
@ -1178,6 +1179,133 @@ public class ChatListViewController: OWSViewController, HomeTabViewController {
|
||||
paymentsBannerView.addSubview(stack)
|
||||
stack.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
func checkForFailedServiceExtensionLaunches() async {
|
||||
guard #available(iOS 17.0, *) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard RemoteConfig.shouldCheckForServiceExtensionFailures else {
|
||||
return
|
||||
}
|
||||
|
||||
await messageProcessor.waitForFetchingAndProcessing().awaitable()
|
||||
|
||||
// Has the NSE ever launched with the current version?
|
||||
let appVersion = AppVersionImpl.shared
|
||||
guard
|
||||
let mainAppVersion = appVersion.lastCompletedLaunchMainAppVersion,
|
||||
let nseAppVersion = appVersion.lastCompletedLaunchNSEAppVersion,
|
||||
let upgradeDate = appVersion.firstMainAppLaunchDateAfterUpdate
|
||||
else {
|
||||
return
|
||||
}
|
||||
guard nseAppVersion != mainAppVersion else {
|
||||
return
|
||||
}
|
||||
|
||||
// Has it been at least an hour since we upgraded?
|
||||
guard -upgradeDate.timeIntervalSinceNow > kHourInterval else {
|
||||
return
|
||||
}
|
||||
|
||||
// Has the user restarted since the most recent update was installed?
|
||||
let bootTime: Date? = {
|
||||
var timeVal = timeval()
|
||||
var timeValSize = MemoryLayout<timeval>.size
|
||||
let err = sysctlbyname("kern.boottime", &timeVal, &timeValSize, nil, 0)
|
||||
guard err == 0, timeValSize == MemoryLayout<timeval>.size else {
|
||||
return nil
|
||||
}
|
||||
return Date(timeIntervalSince1970: TimeInterval(timeVal.tv_sec))
|
||||
}()
|
||||
guard let bootTime else {
|
||||
return
|
||||
}
|
||||
guard bootTime < upgradeDate else {
|
||||
return
|
||||
}
|
||||
|
||||
let keyValueStore = SDSKeyValueStore(collection: "FailedNSELaunches")
|
||||
let mostRecentDateKey = "mostRecentPromptDate"
|
||||
let promptCountKey = "promptCount"
|
||||
|
||||
let shouldShowPrompt = databaseStorage.read { tx in
|
||||
// If we've shown the prompt recently, don't show it again.
|
||||
let promptCount = keyValueStore.getInt(promptCountKey, defaultValue: 0, transaction: tx)
|
||||
let promptBackoff: TimeInterval = {
|
||||
switch promptCount {
|
||||
case 0:
|
||||
return 0
|
||||
case 1, 2:
|
||||
return 24*kHourInterval
|
||||
case 3:
|
||||
return 48*kHourInterval
|
||||
case 4:
|
||||
return 72*kHourInterval
|
||||
default:
|
||||
return 96*kHourInterval
|
||||
}
|
||||
}()
|
||||
let mostRecentDate = keyValueStore.getDate(mostRecentDateKey, transaction: tx)
|
||||
if let mostRecentDate, -mostRecentDate.timeIntervalSinceNow < promptBackoff {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we haven't received a message since upgrading, don't show it.
|
||||
guard
|
||||
let mostRecentMessage = InteractionFinder.lastInsertedIncomingMessage(transaction: tx),
|
||||
Date(millisecondsSince1970: mostRecentMessage.receivedAtTimestamp) > upgradeDate
|
||||
else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
guard shouldShowPrompt else {
|
||||
return
|
||||
}
|
||||
|
||||
guard isChatListTopmostViewController() else {
|
||||
return
|
||||
}
|
||||
|
||||
let actionSheet = ActionSheetController(
|
||||
title: OWSLocalizedString(
|
||||
"NOTIFICATIONS_ERROR_TITLE",
|
||||
comment: "Shown as the title of an alert when notifications can't be shown due to an error."
|
||||
),
|
||||
message: String(
|
||||
format: OWSLocalizedString(
|
||||
"NOTIFICATIONS_ERROR_MESSAGE",
|
||||
comment: "Shown as the body of an alert when notifications can't be shown due to an error."
|
||||
),
|
||||
UIDevice.current.localizedModel
|
||||
)
|
||||
)
|
||||
actionSheet.addAction(ActionSheetAction(
|
||||
title: CommonStrings.contactSupport,
|
||||
handler: { [weak self] _ in
|
||||
guard let self else { return }
|
||||
ContactSupportAlert.presentStep2(emailSupportFilter: "NotLaunchingNSE", fromViewController: self)
|
||||
}
|
||||
))
|
||||
actionSheet.addAction(ActionSheetAction(title: CommonStrings.okButton))
|
||||
|
||||
let promptDate = Date()
|
||||
self.present(actionSheet, animated: true)
|
||||
|
||||
await databaseStorage.awaitableWrite { tx in
|
||||
keyValueStore.setDate(promptDate, key: mostRecentDateKey, transaction: tx)
|
||||
keyValueStore.setInt(
|
||||
keyValueStore.getInt(promptCountKey, defaultValue: 0, transaction: tx) + 1,
|
||||
key: promptCountKey,
|
||||
transaction: tx
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Settings Button
|
||||
|
||||
@ -53,7 +53,9 @@ public class ContactSupportAlert: NSObject {
|
||||
modal.dismiss()
|
||||
}.catch { error in
|
||||
guard !modal.wasCancelled else { return }
|
||||
showError(error, emailSupportFilter: emailSupportFilter, fromViewController: fromViewController)
|
||||
modal.dismiss(completion: {
|
||||
showError(error, emailSupportFilter: emailSupportFilter, fromViewController: fromViewController)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4366,6 +4366,12 @@
|
||||
/* Title for notification permission reminder megaphone */
|
||||
"NOTIFICATION_PERMISSION_REMINDER_MEGAPHONE_TITLE" = "Turn on Notifications?";
|
||||
|
||||
/* Shown as the body of an alert when notifications can't be shown due to an error. */
|
||||
"NOTIFICATIONS_ERROR_MESSAGE" = "Restart your %1$@ if you’re not receiving Signal notifications.";
|
||||
|
||||
/* Shown as the title of an alert when notifications can't be shown due to an error. */
|
||||
"NOTIFICATIONS_ERROR_TITLE" = "Couldn’t Show Notifications";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"NOTIFICATIONS_FOOTER_WARNING" = "Actions include “Mark as Read,” “Reply,” and “Call Back.”";
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ public protocol AppVersion {
|
||||
var lastCompletedLaunchMainAppVersion: String? { get }
|
||||
var lastCompletedLaunchSAEAppVersion: String? { get }
|
||||
var lastCompletedLaunchNSEAppVersion: String? { get }
|
||||
var firstMainAppLaunchDateAfterUpdate: Date? { get }
|
||||
|
||||
var buildDate: Date { get }
|
||||
|
||||
@ -62,6 +63,7 @@ public class AppVersionImpl: AppVersion {
|
||||
private let lastCompletedMainAppLaunchVersionKey = "kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp"
|
||||
private let lastCompletedSAELaunchVersionKey = "kNSUserDefaults_LastCompletedLaunchAppVersion_SAE"
|
||||
private let lastCompletedNSELaunchVersionKey = "kNSUserDefaults_LastCompletedLaunchAppVersion_NSE"
|
||||
private let firstMainAppLaunchDateAfterUpdateKey = "FirstMainAppLaunchDateAfterUpdate"
|
||||
|
||||
public static let shared: AppVersion = {
|
||||
let result = AppVersionImpl(
|
||||
@ -126,8 +128,10 @@ public class AppVersionImpl: AppVersion {
|
||||
public private(set) var lastCompletedLaunchMainAppVersion: String? {
|
||||
get { userDefaults.string(forKey: lastCompletedMainAppLaunchVersionKey) }
|
||||
set {
|
||||
let didChange = lastCompletedLaunchMainAppVersion != newValue
|
||||
userDefaults.setOrRemove(newValue, forKey: lastCompletedLaunchVersionKey)
|
||||
userDefaults.setOrRemove(newValue, forKey: lastCompletedMainAppLaunchVersionKey)
|
||||
if didChange { userDefaults.set(Date(), forKey: firstMainAppLaunchDateAfterUpdateKey) }
|
||||
}
|
||||
}
|
||||
public private(set) var lastCompletedLaunchSAEAppVersion: String? {
|
||||
@ -144,6 +148,9 @@ public class AppVersionImpl: AppVersion {
|
||||
userDefaults.setOrRemove(newValue, forKey: lastCompletedNSELaunchVersionKey)
|
||||
}
|
||||
}
|
||||
public var firstMainAppLaunchDateAfterUpdate: Date? {
|
||||
return userDefaults.object(forKey: firstMainAppLaunchDateAfterUpdateKey) as? Date
|
||||
}
|
||||
|
||||
public let buildDate: Date
|
||||
|
||||
@ -341,6 +348,8 @@ public class MockAppVerion: AppVersion {
|
||||
|
||||
public var lastCompletedLaunchNSEAppVersion: String?
|
||||
|
||||
public var firstMainAppLaunchDateAfterUpdate: Date?
|
||||
|
||||
public var buildDate: Date = Date()
|
||||
|
||||
public func compare(_ lhs: String, with rhs: String) -> ComparisonResult {
|
||||
|
||||
@ -256,6 +256,10 @@ public class RemoteConfig: BaseFlags {
|
||||
return isEnabled(.enableGifSearch, defaultValue: true)
|
||||
}
|
||||
|
||||
public static var shouldCheckForServiceExtensionFailures: Bool {
|
||||
return !isEnabled(.serviceExtensionFailureKillSwitch)
|
||||
}
|
||||
|
||||
// MARK: UInt values
|
||||
|
||||
private static func getUIntValue(
|
||||
@ -475,6 +479,7 @@ private enum IsEnabledFlag: String, FlagType {
|
||||
case cdsDisableCompatibilityMode = "cds.disableCompatibilityMode"
|
||||
case canDonateWithSepa = "ios.canDonateWithSepa"
|
||||
case enableGifSearch = "global.gifSearch"
|
||||
case serviceExtensionFailureKillSwitch = "ios.serviceExtensionFailureKillSwitch"
|
||||
|
||||
var isSticky: Bool {
|
||||
switch self {
|
||||
@ -494,12 +499,15 @@ private enum IsEnabledFlag: String, FlagType {
|
||||
case .ringrtcNwPathMonitorTrialKillSwitch: fallthrough
|
||||
case .cdsDisableCompatibilityMode: fallthrough
|
||||
case .canDonateWithSepa: fallthrough
|
||||
case .enableGifSearch:
|
||||
case .enableGifSearch: fallthrough
|
||||
case .serviceExtensionFailureKillSwitch:
|
||||
return false
|
||||
}
|
||||
}
|
||||
var isHotSwappable: Bool {
|
||||
switch self {
|
||||
case .serviceExtensionFailureKillSwitch:
|
||||
return true
|
||||
case .automaticSessionResetKillSwitch: fallthrough
|
||||
case .paymentsResetKillSwitch: fallthrough
|
||||
case .messageResendKillSwitch: fallthrough
|
||||
|
||||
Loading…
Reference in New Issue
Block a user