diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist
index 8b0394c41..ff99134e5 100644
--- a/Signal/Signal-Info.plist
+++ b/Signal/Signal-Info.plist
@@ -38,7 +38,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.15.1
+ 2.15.2
CFBundleSignature
????
CFBundleURLTypes
@@ -55,7 +55,7 @@
CFBundleVersion
- 2.15.1.0
+ 2.15.2.1
ITSAppUsesNonExemptEncryption
LOGS_EMAIL
diff --git a/Signal/src/ViewControllers/SignalsViewController.m b/Signal/src/ViewControllers/SignalsViewController.m
index 6dba98273..24c87229c 100644
--- a/Signal/src/ViewControllers/SignalsViewController.m
+++ b/Signal/src/ViewControllers/SignalsViewController.m
@@ -728,6 +728,7 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing
callOnViewAppearing:(BOOL)callOnViewAppearing
{
+ // TODO: Do this synchronously if we're already on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
MessagesViewController *mvc = [[MessagesViewController alloc] initWithNibName:@"MessagesViewController"
bundle:nil];
@@ -736,11 +737,7 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
callOnViewAppearing:callOnViewAppearing];
self.lastThread = thread;
- if (self.presentedViewController) {
- [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
- }
- [self.navigationController popToRootViewControllerAnimated:YES];
- [self.navigationController pushViewController:mvc animated:YES];
+ [self pushTopLevelViewController:mvc animateDismissal:YES animatePresentation:YES];
});
}
@@ -799,6 +796,10 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
// Perform the first step.
if (self.presentedViewController) {
+ if ([self.presentedViewController isKindOfClass:[CallViewController class]]) {
+ OWSProdInfo([OWSAnalyticsEvents errorCouldNotPresentViewDueToCall]);
+ return;
+ }
[self.presentedViewController dismissViewControllerAnimated:animateDismissal completion:dismissNavigationBlock];
} else {
dismissNavigationBlock();
diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift
index 4a28852a1..fbdf5e9d0 100644
--- a/Signal/src/call/CallService.swift
+++ b/Signal/src/call/CallService.swift
@@ -144,8 +144,10 @@ protocol CallServiceObserver: class {
if let oldValue = oldValue {
DeviceSleepManager.sharedInstance.removeBlock(blockObject: oldValue)
}
+ stopAnyCallTimer()
if let call = call {
DeviceSleepManager.sharedInstance.addBlock(blockObject: call)
+ self.startCallTimer()
}
}
@@ -894,7 +896,7 @@ protocol CallServiceObserver: class {
* For incoming call, when the local user has chosen to accept the call.
*/
func handleConnectedCall(_ call: SignalCall) {
- Logger.debug("\(TAG) in \(#function)")
+ Logger.info("\(TAG) in \(#function)")
AssertIsOnMainThread()
guard let peerConnectionClient = self.peerConnectionClient else {
@@ -1567,6 +1569,60 @@ protocol CallServiceObserver: class {
remoteVideoTrack:remoteVideoTrack)
}
}
+
+ // MARK: CallViewController Timer
+
+ var activeCallTimer: Timer?
+ func startCallTimer() {
+ AssertIsOnMainThread()
+
+ if self.activeCallTimer != nil {
+ owsFail("\(TAG) activeCallTimer should only be set once per call")
+ self.activeCallTimer!.invalidate()
+ self.activeCallTimer = nil
+ }
+
+ self.activeCallTimer = WeakTimer.scheduledTimer(timeInterval: 1, target: self, userInfo: nil, repeats: true) { [weak self] timer in
+ guard let strongSelf = self else {
+ return
+ }
+
+ guard let call = strongSelf.call else {
+ owsFail("\(strongSelf.TAG) call has since ended. Timer should have been invalidated.")
+ timer.invalidate()
+ return
+ }
+
+ strongSelf.ensureCallScreenPresented(call: call)
+ }
+ }
+
+ func ensureCallScreenPresented(call: SignalCall) {
+ guard let connectedDate = call.connectedDate else {
+ // Ignore; call hasn't connected yet.
+ return
+ }
+
+ let kMaxViewPresentationDelay = 2.5
+ guard fabs(connectedDate.timeIntervalSinceNow) > kMaxViewPresentationDelay else {
+ // Ignore; call connected recently.
+ return
+ }
+
+ guard nil != UIApplication.shared.frontmostViewController as? CallViewController else {
+ OWSProdError(OWSAnalyticsEvents.callServiceCallViewCouldNotPresent(), file:#file, function:#function, line:#line)
+ owsFail("\(TAG) in \(#function) Call terminated due to call view presentation delay.")
+ self.terminateCall()
+ return
+ }
+ }
+
+ func stopAnyCallTimer() {
+ AssertIsOnMainThread()
+
+ self.activeCallTimer?.invalidate()
+ self.activeCallTimer = nil
+ }
}
fileprivate extension MessageSender {
diff --git a/Signal/src/call/NonCallKitCallUIAdaptee.swift b/Signal/src/call/NonCallKitCallUIAdaptee.swift
index 2a4861ca0..7f78e4c54 100644
--- a/Signal/src/call/NonCallKitCallUIAdaptee.swift
+++ b/Signal/src/call/NonCallKitCallUIAdaptee.swift
@@ -31,7 +31,7 @@ class NonCallKitCallUIAdaptee: CallUIAdaptee {
Logger.debug("\(self.TAG) handleOutgoingCall succeeded")
}.catch { error in
Logger.error("\(self.TAG) handleOutgoingCall failed with error: \(error)")
- }
+ }.retainUntilComplete()
return call
}
diff --git a/Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift b/Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift
index b93d1199f..910ba07ff 100644
--- a/Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift
+++ b/Signal/src/call/Speakerbox/CallKitCallUIAdaptee.swift
@@ -67,7 +67,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func startOutgoingCall(handle: String) -> SignalCall {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
let call = SignalCall.outgoingCall(localId: UUID(), remotePhoneNumber: handle)
@@ -82,7 +82,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
// Called from CallService after call has ended to clean up any remaining CallKit call state.
func failCall(_ call: SignalCall, error: CallError) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
switch error {
case .timeout(description: _):
@@ -96,7 +96,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func reportIncomingCall(_ call: SignalCall, callerName: String) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
@@ -130,14 +130,14 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func answerCall(localId: UUID) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
owsFail("\(self.TAG) \(#function) CallKit should answer calls via system call screen, not via notifications.")
}
func answerCall(_ call: SignalCall) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
callManager.answer(call: call)
}
@@ -150,14 +150,14 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func declineCall(_ call: SignalCall) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
callManager.localHangup(call: call)
}
func recipientAcceptedCall(_ call: SignalCall) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
self.provider.reportOutgoingCall(with: call.localId, connectedAt: nil)
@@ -169,28 +169,28 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func localHangupCall(_ call: SignalCall) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
callManager.localHangup(call: call)
}
func remoteDidHangupCall(_ call: SignalCall) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
provider.reportCall(with: call.localId, endedAt: nil, reason: CXCallEndedReason.remoteEnded)
}
func remoteBusy(_ call: SignalCall) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
provider.reportCall(with: call.localId, endedAt: nil, reason: CXCallEndedReason.unanswered)
}
func setIsMuted(call: SignalCall, isMuted: Bool) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
callManager.setIsMuted(call: call, isMuted: isMuted)
}
@@ -212,7 +212,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
AssertIsOnMainThread()
- Logger.debug("\(self.TAG) \(#function)")
+ Logger.info("\(self.TAG) \(#function)")
// Stop any in-progress WebRTC related audio.
PeerConnectionClient.stopAudioSession()
@@ -228,7 +228,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
AssertIsOnMainThread()
- Logger.debug("\(TAG) in \(#function) CXStartCallAction")
+ Logger.info("\(TAG) in \(#function) CXStartCallAction")
guard let call = callManager.callWithLocalId(action.callUUID) else {
Logger.error("\(TAG) unable to find call in \(#function)")
@@ -256,7 +256,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
AssertIsOnMainThread()
- Logger.debug("\(TAG) Received \(#function) CXAnswerCallAction")
+ Logger.info("\(TAG) Received \(#function) CXAnswerCallAction")
// Retrieve the instance corresponding to the action's call UUID
guard let call = callManager.callWithLocalId(action.callUUID) else {
action.fail()
@@ -271,7 +271,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
AssertIsOnMainThread()
- Logger.debug("\(TAG) Received \(#function) CXEndCallAction")
+ Logger.info("\(TAG) Received \(#function) CXEndCallAction")
guard let call = callManager.callWithLocalId(action.callUUID) else {
Logger.error("\(self.TAG) in \(#function) trying to end unknown call with localId: \(action.callUUID)")
action.fail()
@@ -290,7 +290,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
AssertIsOnMainThread()
- Logger.debug("\(TAG) Received \(#function) CXSetHeldCallAction")
+ Logger.info("\(TAG) Received \(#function) CXSetHeldCallAction")
guard let call = callManager.callWithLocalId(action.callUUID) else {
action.fail()
return
@@ -316,7 +316,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
public func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
AssertIsOnMainThread()
- Logger.debug("\(TAG) Received \(#function) CXSetMutedCallAction")
+ Logger.info("\(TAG) Received \(#function) CXSetMutedCallAction")
guard callManager.callWithLocalId(action.callUUID) != nil else {
Logger.error("\(TAG) Failing CXSetMutedCallAction for unknown call: \(action.callUUID)")
action.fail()
@@ -342,7 +342,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
AssertIsOnMainThread()
- Logger.debug("\(TAG) Timed out \(#function) while performing \(action)")
+ owsFail("\(TAG) Timed out \(#function) while performing \(action)")
// React to the action timeout if necessary, such as showing an error UI.
}
diff --git a/Signal/src/call/UserInterface/CallUIAdapter.swift b/Signal/src/call/UserInterface/CallUIAdapter.swift
index b472010ae..c458c5b60 100644
--- a/Signal/src/call/UserInterface/CallUIAdapter.swift
+++ b/Signal/src/call/UserInterface/CallUIAdapter.swift
@@ -34,7 +34,7 @@ extension CallUIAdaptee {
internal func showCall(_ call: SignalCall) {
AssertIsOnMainThread()
- let callViewController = CallViewController(call:call)
+ let callViewController = CallViewController(call: call)
callViewController.modalTransitionStyle = .crossDissolve
guard let presentingViewController = Environment.getCurrent().signalsViewController else {
diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h
index 7a25f0882..fd9bcf55e 100644
--- a/SignalServiceKit/src/Contacts/TSThread.h
+++ b/SignalServiceKit/src/Contacts/TSThread.h
@@ -16,6 +16,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface TSThread : TSYapDatabaseObject
+// YES IFF this thread has ever had a message.
+@property (nonatomic) BOOL hasEverHadMessage;
+
/**
* Whether the object is a group thread or not.
*
diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m
index ff67a2c29..8acc9eead 100644
--- a/SignalServiceKit/src/Contacts/TSThread.m
+++ b/SignalServiceKit/src/Contacts/TSThread.m
@@ -321,6 +321,8 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
+ self.hasEverHadMessage = YES;
+
NSDate *lastMessageDate = [lastMessage dateForSorting];
if (!_lastMessageDate || [lastMessageDate timeIntervalSinceDate:self.lastMessageDate] > 0) {
_lastMessageDate = lastMessageDate;
diff --git a/SignalServiceKit/src/Storage/TSDatabaseView.m b/SignalServiceKit/src/Storage/TSDatabaseView.m
index dabede30b..35ed412d5 100644
--- a/SignalServiceKit/src/Storage/TSDatabaseView.m
+++ b/SignalServiceKit/src/Storage/TSDatabaseView.m
@@ -224,11 +224,17 @@ NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevic
TSThread *thread = (TSThread *)object;
- YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSMessageDatabaseViewExtensionName];
- OWSAssert(viewTransaction);
- NSUInteger threadMessageCount = [viewTransaction numberOfItemsInGroup:thread.uniqueId];
- if (threadMessageCount < 1) {
- return nil;
+ if (thread.isGroupThread) {
+ // Do nothing; we never hide group threads.
+ } else if (thread.hasEverHadMessage) {
+ // Do nothing; we never hide threads that have ever had a message.
+ } else {
+ YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSMessageDatabaseViewExtensionName];
+ OWSAssert(viewTransaction);
+ NSUInteger threadMessageCount = [viewTransaction numberOfItemsInGroup:thread.uniqueId];
+ if (threadMessageCount < 1) {
+ return nil;
+ }
}
if (thread.archivalDate) {
@@ -248,7 +254,7 @@ NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevic
[[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSThread collection]]];
YapDatabaseView *databaseView =
- [[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"2" options:options];
+ [[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"3" options:options];
[[TSStorageManager sharedManager].database registerExtension:databaseView
withName:TSThreadDatabaseViewExtensionName];
diff --git a/SignalServiceKit/src/Util/OWSAnalyticsEvents.h b/SignalServiceKit/src/Util/OWSAnalyticsEvents.h
index c456aa2fb..a2280eb03 100755
--- a/SignalServiceKit/src/Util/OWSAnalyticsEvents.h
+++ b/SignalServiceKit/src/Util/OWSAnalyticsEvents.h
@@ -36,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)callServiceCallUnexpectedlyIdle;
++ (NSString *)callServiceCallViewCouldNotPresent;
+
+ (NSString *)callServiceCouldNotCreatePeerConnectionClientPromise;
+ (NSString *)callServiceCouldNotCreateReadyToSendIceUpdatesPromise;
@@ -72,6 +74,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)errorAttachmentRequestFailed;
++ (NSString *)errorCouldNotPresentViewDueToCall;
+
+ (NSString *)errorEnableVideoCallingRequestFailed;
+ (NSString *)errorGetDevicesFailed;
diff --git a/SignalServiceKit/src/Util/OWSAnalyticsEvents.m b/SignalServiceKit/src/Util/OWSAnalyticsEvents.m
index 8b9e63f1e..257999528 100755
--- a/SignalServiceKit/src/Util/OWSAnalyticsEvents.m
+++ b/SignalServiceKit/src/Util/OWSAnalyticsEvents.m
@@ -72,6 +72,11 @@ NS_ASSUME_NONNULL_BEGIN
return @"call_service_call_unexpectedly_idle";
}
++ (NSString *)callServiceCallViewCouldNotPresent
+{
+ return @"call_service_call_view_could_not_present";
+}
+
+ (NSString *)callServiceCouldNotCreatePeerConnectionClientPromise
{
return @"call_service_could_not_create_peer_connection_client_promise";
@@ -162,6 +167,11 @@ NS_ASSUME_NONNULL_BEGIN
return @"error_attachment_request_failed";
}
++ (NSString *)errorCouldNotPresentViewDueToCall
+{
+ return @"error_could_not_present_view_due_to_call";
+}
+
+ (NSString *)errorEnableVideoCallingRequestFailed
{
return @"error_enable_video_calling_request_failed";