diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 9666ec5b4..2e4347b12 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */; }; 3471B1DA1EB7C63600F6AEC8 /* NewNonContactConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */; }; 3472229F1EB22FFE00E53955 /* AddToGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */; }; + 348F2EAE1F0D21BC00D4ECE0 /* DeviceSleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */; }; 3497DBEC1ECE257500DB2605 /* OWSCountryMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = 3497DBEB1ECE257500DB2605 /* OWSCountryMetadata.m */; }; 3497DBEF1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3497DBEE1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m */; }; 34B3F8711E8DF1700035BE1A /* AboutTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8351E8DF1700035BE1A /* AboutTableViewController.m */; }; @@ -414,6 +415,7 @@ 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewNonContactConversationViewController.m; sourceTree = ""; }; 3472229D1EB22FFE00E53955 /* AddToGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToGroupViewController.h; sourceTree = ""; }; 3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddToGroupViewController.m; sourceTree = ""; }; + 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceSleepManager.swift; sourceTree = ""; }; 3497DBEA1ECE257500DB2605 /* OWSCountryMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCountryMetadata.h; sourceTree = ""; }; 3497DBEB1ECE257500DB2605 /* OWSCountryMetadata.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSCountryMetadata.m; sourceTree = ""; }; 3497DBED1ECE2E4700DB2605 /* DomainFrontingCountryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DomainFrontingCountryViewController.h; sourceTree = ""; }; @@ -1326,6 +1328,7 @@ 76EB04CF18170B33006006FC /* collections */, B90418E4183E9DD40038554A /* DateUtil.h */, B90418E5183E9DD40038554A /* DateUtil.m */, + 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */, 45E615151E8C590B0018AD52 /* DisplayableTextFilter.swift */, 76EB04EA18170B33006006FC /* FunctionalUtil.h */, 76EB04EB18170B33006006FC /* FunctionalUtil.m */, @@ -2126,6 +2129,7 @@ 34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */, 76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */, 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */, + 348F2EAE1F0D21BC00D4ECE0 /* DeviceSleepManager.swift in Sources */, 34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */, 34E8BF3B1EEB208E00F5F4CA /* DebugUIVerification.m in Sources */, 76EB058A18170B33006006FC /* Release.m in Sources */, diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index 8bb29abc0..84e9da8ac 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -3628,6 +3628,18 @@ typedef enum : NSUInteger { self.voiceMessageUUID = nil; } +- (void)setAudioRecorder:(AVAudioRecorder *)audioRecorder +{ + // Prevent device from sleeping while recording a voice message. + if (audioRecorder) { + [DeviceSleepManager.sharedInstance addBlockWithBlockObject:audioRecorder]; + } else if (_audioRecorder) { + [DeviceSleepManager.sharedInstance removeBlockWithBlockObject:_audioRecorder]; + } + + _audioRecorder = audioRecorder; +} + #pragma mark Accessory View - (void)didPressAccessoryButton:(UIButton *)sender diff --git a/Signal/src/ViewControllers/OWSAudioAttachmentPlayer.m b/Signal/src/ViewControllers/OWSAudioAttachmentPlayer.m index 26ca60eb7..1f8b8f948 100644 --- a/Signal/src/ViewControllers/OWSAudioAttachmentPlayer.m +++ b/Signal/src/ViewControllers/OWSAudioAttachmentPlayer.m @@ -82,6 +82,8 @@ NS_ASSUME_NONNULL_BEGIN { [[NSNotificationCenter defaultCenter] removeObserver:self]; + [DeviceSleepManager.sharedInstance removeBlockWithBlockObject:self]; + [self stop]; } @@ -133,6 +135,9 @@ NS_ASSUME_NONNULL_BEGIN selector:@selector(audioPlayerUpdated:) userInfo:nil repeats:YES]; + + // Prevent device from sleeping while playing audio. + [DeviceSleepManager.sharedInstance addBlockWithBlockObject:self]; } - (void)pause @@ -145,6 +150,8 @@ NS_ASSUME_NONNULL_BEGIN [self.audioPlayerPoller invalidate]; [self.delegate setAudioProgress:[self.audioPlayer currentTime] duration:[self.audioPlayer duration]]; [self.delegate setAudioIconToPlay]; + + [DeviceSleepManager.sharedInstance removeBlockWithBlockObject:self]; } - (void)stop @@ -157,6 +164,8 @@ NS_ASSUME_NONNULL_BEGIN [self.delegate setAudioIconToPlay]; self.delegate.isAudioPlaying = NO; self.delegate.isPaused = NO; + + [DeviceSleepManager.sharedInstance removeBlockWithBlockObject:self]; } - (void)togglePlayState diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 3e0cff8be..bd7ead0f0 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -138,7 +138,16 @@ protocol CallServiceObserver: class { call?.addObserverAndSyncState(observer: self) updateIsVideoEnabled() - updateLockTimerEnabling() + + // Prevent device from sleeping while we have an active call. + if oldValue != call { + if let oldValue = oldValue { + DeviceSleepManager.sharedInstance.removeBlock(blockObject: oldValue) + } + if let call = call { + DeviceSleepManager.sharedInstance.addBlock(blockObject: call) + } + } Logger.debug("\(self.TAG) .call setter: \(oldValue?.identifiersForLogs as Optional) -> \(call?.identifiersForLogs as Optional)") @@ -1351,16 +1360,6 @@ protocol CallServiceObserver: class { remoteVideoTrack:remoteVideoTrack) } } - - private func updateLockTimerEnabling() { - AssertIsOnMainThread() - - // Prevent screen from dimming during call. - // - // Note that this state has no effect if app is in the background. - let hasCall = call != nil - UIApplication.shared.isIdleTimerDisabled = hasCall - } } fileprivate extension MessageSender { diff --git a/Signal/src/util/DeviceSleepManager.swift b/Signal/src/util/DeviceSleepManager.swift new file mode 100644 index 000000000..a09b20210 --- /dev/null +++ b/Signal/src/util/DeviceSleepManager.swift @@ -0,0 +1,81 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation + +// This entity has responsibility for blocking the device from sleeping if +// certain behaviors (e.g. recording or playing voice messages) are in progress. +// +// Sleep blocking is keyed using "block objects" whose lifetime correponds to +// the duration of the block. For example, sleep blocking during audio playback +// can be keyed to the audio player. This provides a measure of robustness. +// On the one hand, we can use weak references to track block objects and stop +// blocking if the block object is deallocated even if removeBlock() is not +// called. On the other hand, we will also get correct behavior to addBlock() +// being called twice with the same block object. +@objc class DeviceSleepManager: NSObject { + + let TAG = "[DeviceSleepManager]" + + static let sharedInstance = DeviceSleepManager() + + private class SleepBlock { + weak var blockObject: NSObject? + + init(blockObject: NSObject) { + self.blockObject = blockObject + } + } + private var blocks: [SleepBlock] = [] + + override init() { + super.init() + + NotificationCenter.default.addObserver(self, + selector:#selector(didEnterBackground), + name:NSNotification.Name.UIApplicationDidEnterBackground, + object:nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + func didEnterBackground() { + AssertIsOnMainThread() + + ensureSleepBlocking() + } + + public func addBlock(blockObject: NSObject) { + blocks.append(SleepBlock(blockObject: blockObject)) + + ensureSleepBlocking() + } + + public func removeBlock(blockObject: NSObject) { + blocks = blocks.filter { + $0.blockObject != nil && $0.blockObject != blockObject + } + + ensureSleepBlocking() + } + + private func ensureSleepBlocking() { + // Cull expired blocks. + blocks = blocks.filter { + $0.blockObject != nil + } + let shouldBlock = blocks.count > 0 + + if UIApplication.shared.isIdleTimerDisabled != shouldBlock { + if shouldBlock { + Logger.info("\(self.TAG) \(#function): Blocking sleep") + } else { + Logger.info("\(self.TAG) \(#function): Unblocking sleep") + } + } + UIApplication.shared.isIdleTimerDisabled = shouldBlock + } +}