From 47c0ba2719d1e670988698cdec9031e186ff45c9 Mon Sep 17 00:00:00 2001 From: Khaled Alshehri <39571180+i5xx@users.noreply.github.com> Date: Wed, 8 Aug 2018 02:18:40 +0300 Subject: [PATCH] Add files via upload --- BlackCamera/SCBlackCameraDetector.h | 56 +++++++ BlackCamera/SCBlackCameraDetector.m | 134 +++++++++++++++++ BlackCamera/SCBlackCameraNoOutputDetector.h | 26 ++++ BlackCamera/SCBlackCameraNoOutputDetector.m | 137 ++++++++++++++++++ BlackCamera/SCBlackCameraPreviewDetector.h | 20 +++ BlackCamera/SCBlackCameraPreviewDetector.m | 92 ++++++++++++ BlackCamera/SCBlackCameraReporter.h | 35 +++++ BlackCamera/SCBlackCameraReporter.m | 86 +++++++++++ BlackCamera/SCBlackCameraRunningDetector.h | 27 ++++ BlackCamera/SCBlackCameraRunningDetector.m | 84 +++++++++++ .../SCBlackCameraSessionBlockDetector.h | 23 +++ .../SCBlackCameraSessionBlockDetector.m | 82 +++++++++++ BlackCamera/SCBlackCameraViewDetector.h | 31 ++++ BlackCamera/SCBlackCameraViewDetector.m | 136 +++++++++++++++++ BlackCamera/SCCaptureSessionFixer.h | 14 ++ BlackCamera/SCCaptureSessionFixer.m | 21 +++ ...CContextAwareSnapCreationThrottleRequest.h | 16 ++ ...CContextAwareSnapCreationThrottleRequest.m | 70 +++++++++ .../Triggers/SCSnapCreationTriggers.h | 22 +++ .../Triggers/SCSnapCreationTriggers.m | 83 +++++++++++ 20 files changed, 1195 insertions(+) create mode 100644 BlackCamera/SCBlackCameraDetector.h create mode 100644 BlackCamera/SCBlackCameraDetector.m create mode 100644 BlackCamera/SCBlackCameraNoOutputDetector.h create mode 100644 BlackCamera/SCBlackCameraNoOutputDetector.m create mode 100644 BlackCamera/SCBlackCameraPreviewDetector.h create mode 100644 BlackCamera/SCBlackCameraPreviewDetector.m create mode 100644 BlackCamera/SCBlackCameraReporter.h create mode 100644 BlackCamera/SCBlackCameraReporter.m create mode 100644 BlackCamera/SCBlackCameraRunningDetector.h create mode 100644 BlackCamera/SCBlackCameraRunningDetector.m create mode 100644 BlackCamera/SCBlackCameraSessionBlockDetector.h create mode 100644 BlackCamera/SCBlackCameraSessionBlockDetector.m create mode 100644 BlackCamera/SCBlackCameraViewDetector.h create mode 100644 BlackCamera/SCBlackCameraViewDetector.m create mode 100644 BlackCamera/SCCaptureSessionFixer.h create mode 100644 BlackCamera/SCCaptureSessionFixer.m create mode 100644 ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h create mode 100644 ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m create mode 100644 ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h create mode 100644 ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m diff --git a/BlackCamera/SCBlackCameraDetector.h b/BlackCamera/SCBlackCameraDetector.h new file mode 100644 index 0000000..bc64092 --- /dev/null +++ b/BlackCamera/SCBlackCameraDetector.h @@ -0,0 +1,56 @@ +// +// SCBlackCameraDetector.h +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import "SCBlackCameraReporter.h" + +#import +#import + +@class SCBlackCameraNoOutputDetector; + +@interface SCBlackCameraDetector : NSObject + +@property (nonatomic, strong) SCBlackCameraNoOutputDetector *blackCameraNoOutputDetector; + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithTicketCreator:(id)ticketCreator; + +// CameraView visible/invisible +- (void)onCameraViewVisible:(BOOL)visible; + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)touch; + +// Call this when [AVCaptureSession startRunning] is called +- (void)sessionWillCallStartRunning; +- (void)sessionDidCallStartRunning; + +// Call this when [AVCaptureSession stopRunning] is called +- (void)sessionWillCallStopRunning; +- (void)sessionDidCallStopRunning; + +// Call this when [AVCaptureSession commitConfiguration] is called +- (void)sessionWillCommitConfiguration; +- (void)sessionDidCommitConfiguration; + +- (void)sessionDidChangeIsRunning:(BOOL)running; + +// For CapturePreview visibility detector +- (void)capturePreviewDidBecomeVisible:(BOOL)visible; + +/** + Mark the start of creating new session + When we fix black camera by creating new session, some detector may report black camera because we called + [AVCaptureSession stopRunning] on old AVCaptureSession, so we need to tell the detector the session is recreating, so + it is fine to call [AVCaptureSession stopRunning] on old AVCaptureSession. + */ +- (void)sessionWillRecreate; +/** + Mark the end of creating new session + */ +- (void)sessionDidRecreate; + +@end diff --git a/BlackCamera/SCBlackCameraDetector.m b/BlackCamera/SCBlackCameraDetector.m new file mode 100644 index 0000000..4911000 --- /dev/null +++ b/BlackCamera/SCBlackCameraDetector.m @@ -0,0 +1,134 @@ +// +// SCBlackCameraDetector.m +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import "SCBlackCameraDetector.h" + +#import "SCBlackCameraNoOutputDetector.h" +#import "SCBlackCameraPreviewDetector.h" +#import "SCBlackCameraRunningDetector.h" +#import "SCBlackCameraSessionBlockDetector.h" +#import "SCBlackCameraViewDetector.h" + +#import + +#if !TARGET_IPHONE_SIMULATOR +static char *const kSCBlackCameraDetectorQueueLabel = "com.snapchat.black-camera-detector"; +#endif +@interface SCBlackCameraDetector () { + BOOL _sessionIsRunning; + BOOL _cameraIsVisible; + BOOL _previewIsVisible; +} +@property (nonatomic, strong) SCQueuePerformer *queuePerformer; +@property (nonatomic, strong) SCBlackCameraViewDetector *cameraViewDetector; +@property (nonatomic, strong) SCBlackCameraRunningDetector *sessionRunningDetector; +@property (nonatomic, strong) SCBlackCameraPreviewDetector *previewDetector; +@property (nonatomic, strong) SCBlackCameraSessionBlockDetector *sessionBlockDetector; + +@end + +@implementation SCBlackCameraDetector + +- (instancetype)initWithTicketCreator:(id)ticketCreator +{ +#if !TARGET_IPHONE_SIMULATOR + + self = [super init]; + if (self) { + _queuePerformer = [[SCQueuePerformer alloc] initWithLabel:kSCBlackCameraDetectorQueueLabel + qualityOfService:QOS_CLASS_BACKGROUND + queueType:DISPATCH_QUEUE_SERIAL + context:SCQueuePerformerContextCamera]; + + SCBlackCameraReporter *reporter = [[SCBlackCameraReporter alloc] initWithTicketCreator:ticketCreator]; + _cameraViewDetector = [[SCBlackCameraViewDetector alloc] initWithPerformer:_queuePerformer reporter:reporter]; + _sessionRunningDetector = + [[SCBlackCameraRunningDetector alloc] initWithPerformer:_queuePerformer reporter:reporter]; + _previewDetector = [[SCBlackCameraPreviewDetector alloc] initWithPerformer:_queuePerformer reporter:reporter]; + _sessionBlockDetector = [[SCBlackCameraSessionBlockDetector alloc] initWithReporter:reporter]; + _blackCameraNoOutputDetector = [[SCBlackCameraNoOutputDetector alloc] initWithReporter:reporter]; + } + return self; +#else + return nil; +#endif +} + +#pragma mark - Camera view visibility detector +- (void)onCameraViewVisible:(BOOL)visible +{ + SC_GUARD_ELSE_RETURN(visible != _cameraIsVisible); + _cameraIsVisible = visible; + [_cameraViewDetector onCameraViewVisible:visible]; +} + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture +{ + [_cameraViewDetector onCameraViewVisibleWithTouch:gesture]; +} + +#pragma mark - Track [AVCaptureSession startRunning] call +- (void)sessionWillCallStartRunning +{ + [_cameraViewDetector sessionWillCallStartRunning]; + [_sessionBlockDetector sessionWillCallStartRunning]; +} + +- (void)sessionDidCallStartRunning +{ + [_sessionRunningDetector sessionDidCallStartRunning]; + [_sessionBlockDetector sessionDidCallStartRunning]; +} + +#pragma mark - Track [AVCaptureSession stopRunning] call +- (void)sessionWillCallStopRunning +{ + [_cameraViewDetector sessionWillCallStopRunning]; + [_sessionRunningDetector sessionWillCallStopRunning]; +} + +- (void)sessionDidCallStopRunning +{ +} + +- (void)sessionDidChangeIsRunning:(BOOL)running +{ + SC_GUARD_ELSE_RETURN(running != _sessionIsRunning); + _sessionIsRunning = running; + [_sessionRunningDetector sessionDidChangeIsRunning:running]; + [_previewDetector sessionDidChangeIsRunning:running]; +} + +#pragma mark - Capture preview visibility detector +- (void)capturePreviewDidBecomeVisible:(BOOL)visible +{ + SC_GUARD_ELSE_RETURN(visible != _previewIsVisible); + _previewIsVisible = visible; + [_previewDetector capturePreviewDidBecomeVisible:visible]; +} + +#pragma mark - AVCaptureSession block detector +- (void)sessionWillCommitConfiguration +{ + [_sessionBlockDetector sessionWillCommitConfiguration]; +} + +- (void)sessionDidCommitConfiguration +{ + [_sessionBlockDetector sessionDidCommitConfiguration]; +} + +- (void)sessionWillRecreate +{ + [_cameraViewDetector sessionWillRecreate]; +} + +- (void)sessionDidRecreate +{ + [_cameraViewDetector sessionDidRecreate]; +} +@end diff --git a/BlackCamera/SCBlackCameraNoOutputDetector.h b/BlackCamera/SCBlackCameraNoOutputDetector.h new file mode 100644 index 0000000..d9de8c7 --- /dev/null +++ b/BlackCamera/SCBlackCameraNoOutputDetector.h @@ -0,0 +1,26 @@ +// +// SCBlackCameraNoOutputDetector.h +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// + +#import "SCManagedCapturerListener.h" + +#import + +#import + +@class SCBlackCameraNoOutputDetector, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@protocol SCBlackCameraDetectorDelegate +- (void)detector:(SCBlackCameraNoOutputDetector *)detector didDetectBlackCamera:(id)capture; +@end + +@interface SCBlackCameraNoOutputDetector : NSObject + +@property (nonatomic, weak) id delegate; +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter; + +@end diff --git a/BlackCamera/SCBlackCameraNoOutputDetector.m b/BlackCamera/SCBlackCameraNoOutputDetector.m new file mode 100644 index 0000000..2b11b01 --- /dev/null +++ b/BlackCamera/SCBlackCameraNoOutputDetector.m @@ -0,0 +1,137 @@ +// +// SCBlackCameraDetectorNoOutput.m +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// +// This detector is used to detect the case that session is running, but there is no sample buffer output + +#import "SCBlackCameraNoOutputDetector.h" + +#import "SCBlackCameraReporter.h" + +#import +#import +#import +#import +#import +#import +#import + +static CGFloat const kShortCheckingDelay = 0.5f; +static CGFloat const kLongCheckingDelay = 3.0f; +static char *const kSCBlackCameraDetectorQueueLabel = "com.snapchat.black-camera-detector"; + +@interface SCBlackCameraNoOutputDetector () { + BOOL _sampleBufferReceived; + BOOL _blackCameraDetected; + // Whether we receive first frame after we detected black camera, that's maybe because the checking delay is too + // short, and we will switch to kLongCheckingDelay next time we do the checking + BOOL _blackCameraRecovered; + // Whether checking is scheduled, to avoid duplicated checking + BOOL _checkingScheduled; + // Whether AVCaptureSession is stopped, if stopped, we don't need to check black camera any more + // It is set on main thread, read on background queue + BOOL _sessionStoppedRunning; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; +@end + +@implementation SCBlackCameraNoOutputDetector + +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = [[SCQueuePerformer alloc] initWithLabel:kSCBlackCameraDetectorQueueLabel + qualityOfService:QOS_CLASS_BACKGROUND + queueType:DISPATCH_QUEUE_SERIAL + context:SCQueuePerformerContextCamera]; + _reporter = reporter; + } + return self; +} + +- (void)managedVideoDataSource:(id)managedVideoDataSource + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + devicePosition:(SCManagedCaptureDevicePosition)devicePosition +{ + // The block is very light-weight + [self.queuePerformer perform:^{ + if (_blackCameraDetected) { + // Detected a black camera case + _blackCameraDetected = NO; + _blackCameraRecovered = YES; + SCLogCoreCameraInfo(@"[BlackCamera] Black camera recovered"); + if (SCExperimentWithBlackCameraReporting()) { + [[SCLogger sharedInstance] logUnsampledEvent:KSCCameraBlackCamera + parameters:@{ + @"type" : @"RECOVERED" + } + secretParameters:nil + metrics:nil]; + } + } + + // Received buffer! + _sampleBufferReceived = YES; + }]; +} + +- (void)managedCapturer:(id)managedCapturer didStartRunning:(SCManagedCapturerState *)state +{ + SCAssertMainThread(); + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { + SCLogCoreCameraInfo(@"[BlackCamera] In background, skip checking"); + return; + } + _sessionStoppedRunning = NO; + [self.queuePerformer perform:^{ + SCTraceODPCompatibleStart(2); + if (_checkingScheduled) { + SCLogCoreCameraInfo(@"[BlackCamera] Checking is scheduled, skip"); + return; + } + if (_sessionStoppedRunning) { + SCLogCoreCameraInfo(@"[BlackCamera] AVCaptureSession stopped, should not check"); + return; + } + _sampleBufferReceived = NO; + if (_blackCameraRecovered) { + SCLogCoreCameraInfo(@"[BlackCamera] Last black camera recovered, let's wait longer to check this time"); + } + SCLogCoreCameraInfo(@"[BlackCamera] Schedule black camera checking"); + [self.queuePerformer perform:^{ + SCTraceODPCompatibleStart(2); + if (!_sessionStoppedRunning) { + if (!_sampleBufferReceived) { + _blackCameraDetected = YES; + [_reporter reportBlackCameraWithCause:SCBlackCameraNoOutputData]; + [self.delegate detector:self didDetectBlackCamera:managedCapturer]; + } else { + SCLogCoreCameraInfo(@"[BlackCamera] No black camera"); + _blackCameraDetected = NO; + } + } else { + SCLogCoreCameraInfo(@"[BlackCamera] AVCaptureSession stopped"); + _blackCameraDetected = NO; + } + _blackCameraRecovered = NO; + _checkingScheduled = NO; + } + after:_blackCameraRecovered ? kLongCheckingDelay : kShortCheckingDelay]; + _checkingScheduled = YES; + }]; +} +- (void)managedCapturer:(id)managedCapturer didStopRunning:(SCManagedCapturerState *)state +{ + SCAssertMainThread(); + _sessionStoppedRunning = YES; + [self.queuePerformer perform:^{ + SCTraceODPCompatibleStart(2); + _sampleBufferReceived = NO; + }]; +} + +@end diff --git a/BlackCamera/SCBlackCameraPreviewDetector.h b/BlackCamera/SCBlackCameraPreviewDetector.h new file mode 100644 index 0000000..f628320 --- /dev/null +++ b/BlackCamera/SCBlackCameraPreviewDetector.h @@ -0,0 +1,20 @@ +// +// SCBlackCameraPreviewDetector.h +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import + +@class SCQueuePerformer, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraPreviewDetector : NSObject + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter; + +- (void)sessionDidChangeIsRunning:(BOOL)running; +- (void)capturePreviewDidBecomeVisible:(BOOL)visible; + +@end diff --git a/BlackCamera/SCBlackCameraPreviewDetector.m b/BlackCamera/SCBlackCameraPreviewDetector.m new file mode 100644 index 0000000..5d8785e --- /dev/null +++ b/BlackCamera/SCBlackCameraPreviewDetector.m @@ -0,0 +1,92 @@ +// +// SCBlackCameraPreviewDetector.m +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import "SCBlackCameraPreviewDetector.h" + +#import "SCBlackCameraReporter.h" +#import "SCMetalUtils.h" + +#import +#import +#import +#import + +// Check whether preview is visible when AVCaptureSession is running +static CGFloat const kSCBlackCameraCheckingDelay = 0.5; + +@interface SCBlackCameraPreviewDetector () { + BOOL _previewVisible; + dispatch_block_t _checkingBlock; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; + +@end + +@implementation SCBlackCameraPreviewDetector + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = performer; + _reporter = reporter; + } + return self; +} + +- (void)capturePreviewDidBecomeVisible:(BOOL)visible +{ + [_queuePerformer perform:^{ + _previewVisible = visible; + }]; +} + +- (void)sessionDidChangeIsRunning:(BOOL)running +{ + if (running) { + [self _scheduleCheck]; + } else { + [_queuePerformer perform:^{ + if (_checkingBlock) { + dispatch_block_cancel(_checkingBlock); + _checkingBlock = nil; + } + }]; + } +} + +- (void)_scheduleCheck +{ + [_queuePerformer perform:^{ + @weakify(self); + _checkingBlock = dispatch_block_create(0, ^{ + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + self->_checkingBlock = nil; + [self _checkPreviewState]; + }); + [_queuePerformer perform:_checkingBlock after:kSCBlackCameraCheckingDelay]; + }]; +} + +- (void)_checkPreviewState +{ + if (!_previewVisible) { + runOnMainThreadAsynchronously(^{ + // Make sure the app is in foreground + SC_GUARD_ELSE_RETURN([UIApplication sharedApplication].applicationState == UIApplicationStateActive); + + SCBlackCameraCause cause = + SCDeviceSupportsMetal() ? SCBlackCameraRenderingPaused : SCBlackCameraPreviewIsHidden; + [_reporter reportBlackCameraWithCause:cause]; + [_reporter fileShakeTicketWithCause:cause]; + }); + } +} + +@end diff --git a/BlackCamera/SCBlackCameraReporter.h b/BlackCamera/SCBlackCameraReporter.h new file mode 100644 index 0000000..f7c36dc --- /dev/null +++ b/BlackCamera/SCBlackCameraReporter.h @@ -0,0 +1,35 @@ +// +// SCBlackCameraReporter.h +// Snapchat +// +// Created by Derek Wang on 09/01/2018. +// + +#import + +#import + +typedef NS_ENUM(NSInteger, SCBlackCameraCause) { + SCBlackCameraStartRunningNotCalled, // 1. View is visible, but session startRunning is not called + SCBlackCameraSessionNotRunning, // 2. Session startRunning is called, but isRunning is still false + SCBlackCameraRenderingPaused, // 3.1 View is visible, but capture preview rendering is paused + SCBlackCameraPreviewIsHidden, // 3.2 For non-metal devices, capture preview is hidden + SCBlackCameraSessionStartRunningBlocked, // 4.1 AVCaptureSession is blocked at startRunning + SCBlackCameraSessionConfigurationBlocked, // 4.2 AVCaptureSession is blocked at commitConfiguration + + SCBlackCameraNoOutputData, // 5. Session is running, but no data output +}; + +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraReporter : NSObject + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithTicketCreator:(id)ticketCreator; + +- (NSString *)causeNameFor:(SCBlackCameraCause)cause; + +- (void)reportBlackCameraWithCause:(SCBlackCameraCause)cause; +- (void)fileShakeTicketWithCause:(SCBlackCameraCause)cause; + +@end diff --git a/BlackCamera/SCBlackCameraReporter.m b/BlackCamera/SCBlackCameraReporter.m new file mode 100644 index 0000000..e997818 --- /dev/null +++ b/BlackCamera/SCBlackCameraReporter.m @@ -0,0 +1,86 @@ +// +// SCBlackCameraReporter.m +// Snapchat +// +// Created by Derek Wang on 09/01/2018. +// + +#import "SCBlackCameraReporter.h" + +#import "SCManiphestTicketCreator.h" + +#import +#import +#import +#import +#import +#import + +@interface SCBlackCameraReporter () + +@property (nonatomic) id ticketCreator; + +@end + +@implementation SCBlackCameraReporter + +- (instancetype)initWithTicketCreator:(id)ticketCreator +{ + if (self = [super init]) { + _ticketCreator = ticketCreator; + } + return self; +} + +- (NSString *)causeNameFor:(SCBlackCameraCause)cause +{ + switch (cause) { + case SCBlackCameraStartRunningNotCalled: + return @"StartRunningNotCalled"; + case SCBlackCameraSessionNotRunning: + return @"SessionNotRunning"; + case SCBlackCameraRenderingPaused: + return @"RenderingPause"; + case SCBlackCameraPreviewIsHidden: + return @"PreviewIsHidden"; + case SCBlackCameraSessionStartRunningBlocked: + return @"SessionStartRunningBlocked"; + case SCBlackCameraSessionConfigurationBlocked: + return @"SessionConfigurationBlocked"; + case SCBlackCameraNoOutputData: + return @"NoOutputData"; + default: + SCAssert(NO, @"illegate cause"); + break; + } + return nil; +} + +- (void)reportBlackCameraWithCause:(SCBlackCameraCause)cause +{ + NSString *causeStr = [self causeNameFor:cause]; + SCLogCoreCameraError(@"[BlackCamera] Detected black camera, cause: %@", causeStr); + + NSDictionary *parameters = @{ @"type" : @"DETECTED", @"cause" : causeStr }; + + [_ticketCreator createAndFileBetaReport:JSONStringSerializeObjectForLogging(parameters)]; + + if (SCExperimentWithBlackCameraReporting()) { + [[SCLogger sharedInstance] logUnsampledEvent:KSCCameraBlackCamera + parameters:parameters + secretParameters:nil + metrics:nil]; + } +} + +- (void)fileShakeTicketWithCause:(SCBlackCameraCause)cause +{ + if (SCExperimentWithBlackCameraExceptionLogging()) { + // Log exception with auto S2R + NSString *errMsg = + [NSString sc_stringWithFormat:@"[BlackCamera] Detected black camera, cause: %@", [self causeNameFor:cause]]; + [_ticketCreator createAndFile:nil creationTime:0 description:errMsg email:nil project:@"Camera" subproject:nil]; + } +} + +@end diff --git a/BlackCamera/SCBlackCameraRunningDetector.h b/BlackCamera/SCBlackCameraRunningDetector.h new file mode 100644 index 0000000..462cabb --- /dev/null +++ b/BlackCamera/SCBlackCameraRunningDetector.h @@ -0,0 +1,27 @@ +// +// SCBlackCameraRunningDetector.h +// Snapchat +// +// Created by Derek Wang on 30/01/2018. +// + +#import + +#import + +@class SCQueuePerformer, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraRunningDetector : NSObject + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter; + +// When session isRunning changed +- (void)sessionDidChangeIsRunning:(BOOL)running; +// Call this after [AVCaptureSession startRunning] is called +- (void)sessionDidCallStartRunning; +// Call this before [AVCaptureSession stopRunning] is called +- (void)sessionWillCallStopRunning; + +@end diff --git a/BlackCamera/SCBlackCameraRunningDetector.m b/BlackCamera/SCBlackCameraRunningDetector.m new file mode 100644 index 0000000..a690d5b --- /dev/null +++ b/BlackCamera/SCBlackCameraRunningDetector.m @@ -0,0 +1,84 @@ +// +// SCBlackCameraRunningDetector.m +// Snapchat +// +// Created by Derek Wang on 30/01/2018. +// + +#import "SCBlackCameraRunningDetector.h" + +#import "SCBlackCameraReporter.h" + +#import +#import +#import +#import + +// Check whether we called AVCaptureSession isRunning within this period +static CGFloat const kSCBlackCameraCheckingDelay = 5; + +@interface SCBlackCameraRunningDetector () { + BOOL _isSessionRunning; + dispatch_block_t _checkSessionBlock; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; +@end + +@implementation SCBlackCameraRunningDetector + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = performer; + _reporter = reporter; + } + return self; +} + +- (void)sessionDidChangeIsRunning:(BOOL)running +{ + [_queuePerformer perform:^{ + _isSessionRunning = running; + }]; +} + +- (void)sessionDidCallStartRunning +{ + [self _scheduleCheck]; +} + +- (void)sessionWillCallStopRunning +{ + [_queuePerformer perform:^{ + if (_checkSessionBlock) { + dispatch_block_cancel(_checkSessionBlock); + _checkSessionBlock = nil; + } + }]; +} + +- (void)_scheduleCheck +{ + [_queuePerformer perform:^{ + @weakify(self); + _checkSessionBlock = dispatch_block_create(0, ^{ + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + self->_checkSessionBlock = nil; + [self _checkSessionState]; + }); + + [_queuePerformer perform:_checkSessionBlock after:kSCBlackCameraCheckingDelay]; + }]; +} + +- (void)_checkSessionState +{ + if (!_isSessionRunning) { + [_reporter reportBlackCameraWithCause:SCBlackCameraSessionNotRunning]; + } +} + +@end diff --git a/BlackCamera/SCBlackCameraSessionBlockDetector.h b/BlackCamera/SCBlackCameraSessionBlockDetector.h new file mode 100644 index 0000000..3da00ca --- /dev/null +++ b/BlackCamera/SCBlackCameraSessionBlockDetector.h @@ -0,0 +1,23 @@ +// +// SCBlackCameraSessionBlockDetector.h +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import "SCBlackCameraReporter.h" + +#import + +@interface SCBlackCameraSessionBlockDetector : NSObject + +SC_INIT_AND_NEW_UNAVAILABLE +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter; + +- (void)sessionWillCallStartRunning; +- (void)sessionDidCallStartRunning; + +- (void)sessionWillCommitConfiguration; +- (void)sessionDidCommitConfiguration; + +@end diff --git a/BlackCamera/SCBlackCameraSessionBlockDetector.m b/BlackCamera/SCBlackCameraSessionBlockDetector.m new file mode 100644 index 0000000..962f086 --- /dev/null +++ b/BlackCamera/SCBlackCameraSessionBlockDetector.m @@ -0,0 +1,82 @@ +// +// SCBlackCameraSessionBlockDetector.m +// Snapchat +// +// Created by Derek Wang on 25/01/2018. +// + +#import "SCBlackCameraSessionBlockDetector.h" + +#import "SCBlackCameraReporter.h" + +#import +#import + +@import CoreGraphics; + +// Longer than 5 seconds is considerred as black camera +static CGFloat const kSCBlackCameraBlockingThreshold = 5; +// Will report if session blocks longer than 1 second +static CGFloat const kSCSessionBlockingLogThreshold = 1; + +@interface SCBlackCameraSessionBlockDetector () { + NSTimeInterval _startTime; +} +@property (nonatomic) SCBlackCameraReporter *reporter; + +@end + +@implementation SCBlackCameraSessionBlockDetector + +- (instancetype)initWithReporter:(SCBlackCameraReporter *)reporter +{ + if (self = [super init]) { + _reporter = reporter; + } + return self; +} + +- (void)sessionWillCallStartRunning +{ + _startTime = [NSDate timeIntervalSinceReferenceDate]; +} + +- (void)sessionDidCallStartRunning +{ + [self _reportBlackCameraIfNeededWithCause:SCBlackCameraSessionStartRunningBlocked]; + [self _reportBlockingIfNeededWithCause:SCBlackCameraSessionStartRunningBlocked]; +} + +- (void)sessionWillCommitConfiguration +{ + _startTime = [NSDate timeIntervalSinceReferenceDate]; +} + +- (void)sessionDidCommitConfiguration +{ + [self _reportBlackCameraIfNeededWithCause:SCBlackCameraSessionConfigurationBlocked]; + [self _reportBlockingIfNeededWithCause:SCBlackCameraSessionConfigurationBlocked]; +} + +- (void)_reportBlockingIfNeededWithCause:(SCBlackCameraCause)cause +{ + NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - _startTime; + if (duration >= kSCSessionBlockingLogThreshold) { + NSString *causeStr = [_reporter causeNameFor:cause]; + [[SCLogger sharedInstance] logEvent:KSCCameraCaptureSessionBlocked + parameters:@{ + @"cause" : causeStr, + @"duration" : @(duration) + }]; + } +} + +- (void)_reportBlackCameraIfNeededWithCause:(SCBlackCameraCause)cause +{ + NSTimeInterval endTime = [NSDate timeIntervalSinceReferenceDate]; + if (endTime - _startTime >= kSCBlackCameraBlockingThreshold) { + [_reporter reportBlackCameraWithCause:cause]; + } +} + +@end diff --git a/BlackCamera/SCBlackCameraViewDetector.h b/BlackCamera/SCBlackCameraViewDetector.h new file mode 100644 index 0000000..912d514 --- /dev/null +++ b/BlackCamera/SCBlackCameraViewDetector.h @@ -0,0 +1,31 @@ +// +// SCBlackCameraDetectorCameraView.h +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import +#import + +@class SCQueuePerformer, SCBlackCameraReporter; +@protocol SCManiphestTicketCreator; + +@interface SCBlackCameraViewDetector : NSObject + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter; + +// CameraView visible/invisible +- (void)onCameraViewVisible:(BOOL)visible; + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture; + +// Call this when [AVCaptureSession startRunning] is called +- (void)sessionWillCallStartRunning; +// Call this when [AVCaptureSession stopRunning] is called +- (void)sessionWillCallStopRunning; + +- (void)sessionWillRecreate; +- (void)sessionDidRecreate; + +@end diff --git a/BlackCamera/SCBlackCameraViewDetector.m b/BlackCamera/SCBlackCameraViewDetector.m new file mode 100644 index 0000000..3815715 --- /dev/null +++ b/BlackCamera/SCBlackCameraViewDetector.m @@ -0,0 +1,136 @@ +// +// SCBlackCameraDetectorCameraView.m +// Snapchat +// +// Created by Derek Wang on 24/01/2018. +// + +#import "SCBlackCameraViewDetector.h" + +#import "SCBlackCameraReporter.h" +#import "SCCaptureDeviceAuthorization.h" + +#import +#import +#import +#import +#import + +// Check whether we called [AVCaptureSession startRunning] within this period +static CGFloat const kSCBlackCameraCheckingDelay = 0.5; + +@interface SCBlackCameraViewDetector () { + BOOL _startRunningCalled; + BOOL _sessionIsRecreating; + dispatch_block_t _checkSessionBlock; +} +@property (nonatomic) SCQueuePerformer *queuePerformer; +@property (nonatomic) SCBlackCameraReporter *reporter; +@property (nonatomic, weak) UIGestureRecognizer *cameraViewGesture; +@end + +@implementation SCBlackCameraViewDetector + +- (instancetype)initWithPerformer:(SCQueuePerformer *)performer reporter:(SCBlackCameraReporter *)reporter +{ + self = [super init]; + if (self) { + _queuePerformer = performer; + _reporter = reporter; + } + return self; +} + +#pragma mark - Camera view visibility change trigger +- (void)onCameraViewVisible:(BOOL)visible +{ + SCTraceODPCompatibleStart(2); + SCLogCoreCameraInfo(@"[BlackCamera] onCameraViewVisible: %d", visible); + BOOL firstTimeAccess = [SCCaptureDeviceAuthorization notDeterminedForVideoCapture]; + if (firstTimeAccess) { + // We don't want to check black camera for firstTimeAccess + return; + } + // Visible and application is active + if (visible && [UIApplication sharedApplication].applicationState == UIApplicationStateActive) { + // Since this method is usually called before the view is actually visible, leave some margin to check + [self _scheduleCheckDelayed:YES]; + } else { + [_queuePerformer perform:^{ + if (_checkSessionBlock) { + dispatch_block_cancel(_checkSessionBlock); + _checkSessionBlock = nil; + } + }]; + } +} + +// Call this when [AVCaptureSession startRunning] is called +- (void)sessionWillCallStartRunning +{ + [_queuePerformer perform:^{ + _startRunningCalled = YES; + }]; +} + +- (void)sessionWillCallStopRunning +{ + [_queuePerformer perform:^{ + _startRunningCalled = NO; + }]; +} + +- (void)_scheduleCheckDelayed:(BOOL)delay +{ + [_queuePerformer perform:^{ + SC_GUARD_ELSE_RETURN(!_checkSessionBlock); + @weakify(self); + _checkSessionBlock = dispatch_block_create(0, ^{ + @strongify(self); + SC_GUARD_ELSE_RETURN(self); + self->_checkSessionBlock = nil; + [self _checkSessionState]; + }); + + if (delay) { + [_queuePerformer perform:_checkSessionBlock after:kSCBlackCameraCheckingDelay]; + } else { + [_queuePerformer perform:_checkSessionBlock]; + } + }]; +} + +- (void)_checkSessionState +{ + SCLogCoreCameraInfo(@"[BlackCamera] checkSessionState startRunning: %d, sessionIsRecreating: %d", + _startRunningCalled, _sessionIsRecreating); + if (!_startRunningCalled && !_sessionIsRecreating) { + [_reporter reportBlackCameraWithCause:SCBlackCameraStartRunningNotCalled]; + [_reporter fileShakeTicketWithCause:SCBlackCameraStartRunningNotCalled]; + } +} + +- (void)sessionWillRecreate +{ + [_queuePerformer perform:^{ + _sessionIsRecreating = YES; + }]; +} + +- (void)sessionDidRecreate +{ + [_queuePerformer perform:^{ + _sessionIsRecreating = NO; + }]; +} + +- (void)onCameraViewVisibleWithTouch:(UIGestureRecognizer *)gesture +{ + if (gesture != _cameraViewGesture) { + // Skip repeating gesture + self.cameraViewGesture = gesture; + [self _scheduleCheckDelayed:NO]; + } +} + +@end diff --git a/BlackCamera/SCCaptureSessionFixer.h b/BlackCamera/SCCaptureSessionFixer.h new file mode 100644 index 0000000..450a482 --- /dev/null +++ b/BlackCamera/SCCaptureSessionFixer.h @@ -0,0 +1,14 @@ +// +// SCCaptureSessionFixer.h +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// + +#import "SCBlackCameraNoOutputDetector.h" + +#import + +@interface SCCaptureSessionFixer : NSObject + +@end diff --git a/BlackCamera/SCCaptureSessionFixer.m b/BlackCamera/SCCaptureSessionFixer.m new file mode 100644 index 0000000..6c87362 --- /dev/null +++ b/BlackCamera/SCCaptureSessionFixer.m @@ -0,0 +1,21 @@ +// +// SCCaptureSessionFixer.m +// Snapchat +// +// Created by Derek Wang on 05/12/2017. +// + +#import "SCCaptureSessionFixer.h" + +#import "SCCameraTweaks.h" + +@implementation SCCaptureSessionFixer + +- (void)detector:(SCBlackCameraNoOutputDetector *)detector didDetectBlackCamera:(id)capture +{ + if (SCCameraTweaksBlackCameraRecoveryEnabled()) { + [capture recreateAVCaptureSession]; + } +} + +@end diff --git a/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h new file mode 100644 index 0000000..f1c16e3 --- /dev/null +++ b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.h @@ -0,0 +1,16 @@ +// +// SCContextAwareSnapCreationThrottleRequest.h +// SCCamera +// +// Created by Cheng Jiang on 4/24/18. +// + +#import + +#import + +@interface SCContextAwareSnapCreationThrottleRequest : NSObject + +- (instancetype)init; + +@end diff --git a/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m new file mode 100644 index 0000000..a2a53ef --- /dev/null +++ b/ContextAwareTaskManagement/Requests/SCContextAwareSnapCreationThrottleRequest.m @@ -0,0 +1,70 @@ +// +// SCContextAwareSnapCreationThrottleRequest.m +// SCCamera +// +// Created by Cheng Jiang on 4/24/18. +// + +#import "SCContextAwareSnapCreationThrottleRequest.h" + +#import +#import +#import + +#import + +BOOL SCCATMSnapCreationEnabled(void) +{ + static dispatch_once_t capturingOnceToken; + static BOOL capturingImprovementEnabled; + dispatch_once(&capturingOnceToken, ^{ + BOOL enabledWithAB = SCExperimentWithContextAwareTaskManagementCapturingImprovementEnabled(); + NSInteger tweakOption = [FBTweakValue(@"CATM", @"Performance Improvement", @"Capturing", (id) @0, + (@{ @0 : @"Respect A/B", + @1 : @"YES", + @2 : @"NO" })) integerValue]; + switch (tweakOption) { + case 0: + capturingImprovementEnabled = enabledWithAB; + break; + case 1: + capturingImprovementEnabled = YES; + break; + case 2: + capturingImprovementEnabled = NO; + break; + default: + SCCAssertFail(@"Illegal option"); + } + }); + return capturingImprovementEnabled; +} + +@implementation SCContextAwareSnapCreationThrottleRequest { + NSString *_requestID; +} + +- (instancetype)init +{ + if (self = [super init]) { + _requestID = @"SCContextAwareSnapCreationThrottleRequest"; + } + return self; +} + +- (BOOL)shouldThrottle:(SCApplicationContextState)context +{ + return SCCATMSnapCreationEnabled() && context != SCApplicationContextStateCamera; +} + +- (NSString *)requestID +{ + return _requestID; +} + +- (BOOL)isEqual:(id)object +{ + return [[object requestID] isEqualToString:_requestID]; +} + +@end diff --git a/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h new file mode 100644 index 0000000..aaf1c89 --- /dev/null +++ b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.h @@ -0,0 +1,22 @@ +// +// SCSnapCreationTriggers.h +// Snapchat +// +// Created by Cheng Jiang on 4/1/18. +// + +#import + +@interface SCSnapCreationTriggers : NSObject + +- (void)markSnapCreationStart; + +- (void)markSnapCreationPreviewAnimationFinish; + +- (void)markSnapCreationPreviewImageSetupFinish; + +- (void)markSnapCreationPreviewVideoFirstFrameRenderFinish; + +- (void)markSnapCreationEndWithContext:(NSString *)context; + +@end diff --git a/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m new file mode 100644 index 0000000..252067a --- /dev/null +++ b/ContextAwareTaskManagement/Triggers/SCSnapCreationTriggers.m @@ -0,0 +1,83 @@ +// +// SCSnapCreationTriggers.m +// Snapchat +// +// Created by Cheng Jiang on 3/30/18. +// + +#import "SCSnapCreationTriggers.h" + +#import "SCContextAwareSnapCreationThrottleRequest.h" + +#import +#import +#import +#import + +@implementation SCSnapCreationTriggers { + BOOL _snapCreationStarted; + BOOL _previewAnimationFinished; + BOOL _previewImageSetupFinished; + BOOL _previewVideoFirstFrameRendered; +} + +- (void)markSnapCreationStart +{ + SC_GUARD_ELSE_RUN_AND_RETURN( + !_snapCreationStarted, + SCLogCoreCameraWarning(@"markSnapCreationStart skipped because previous SnapCreation session is not complete")); + @synchronized(self) + { + _snapCreationStarted = YES; + } + [[SCContextAwareThrottleRequester shared] submitSuspendRequest:[SCContextAwareSnapCreationThrottleRequest new]]; +} + +- (void)markSnapCreationPreviewAnimationFinish +{ + @synchronized(self) + { + _previewAnimationFinished = YES; + if (_previewImageSetupFinished || _previewVideoFirstFrameRendered) { + [self markSnapCreationEndWithContext:@"markSnapCreationPreviewAnimationFinish"]; + } + } +} + +- (void)markSnapCreationPreviewImageSetupFinish +{ + @synchronized(self) + { + _previewImageSetupFinished = YES; + if (_previewAnimationFinished) { + [self markSnapCreationEndWithContext:@"markSnapCreationPreviewImageSetupFinish"]; + } + } +} + +- (void)markSnapCreationPreviewVideoFirstFrameRenderFinish +{ + @synchronized(self) + { + _previewVideoFirstFrameRendered = YES; + if (_previewAnimationFinished) { + [self markSnapCreationEndWithContext:@"markSnapCreationPreviewVideoFirstFrameRenderFinish"]; + } + } +} + +- (void)markSnapCreationEndWithContext:(NSString *)context +{ + SC_GUARD_ELSE_RETURN(_snapCreationStarted); + SCLogCoreCameraInfo(@"markSnapCreationEnd triggered with context: %@", context); + @synchronized(self) + { + _snapCreationStarted = NO; + _previewAnimationFinished = NO; + _previewImageSetupFinished = NO; + _previewVideoFirstFrameRendered = NO; + } + [[SCContextAwareThrottleRequester shared] submitResumeRequest:[SCContextAwareSnapCreationThrottleRequest new]]; +} + +@end