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";