diff options
Diffstat (limited to 'ios/WALT/ScreenResponseController.m')
-rw-r--r-- | ios/WALT/ScreenResponseController.m | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/ios/WALT/ScreenResponseController.m b/ios/WALT/ScreenResponseController.m new file mode 100644 index 0000000..c88236c --- /dev/null +++ b/ios/WALT/ScreenResponseController.m @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "ScreenResponseController.h" + +#include <stdatomic.h> + +#import "NSArray+Extensions.h" +#import "UIAlertView+Extensions.h" +#import "WALTAppDelegate.h" +#import "WALTClient.h" +#import "WALTLogger.h" + +static const NSUInteger kMaxFlashes = 20; // TODO(pquinn): Make this user-configurable. +static const NSTimeInterval kFlashingInterval = 0.1; +static const char kWALTScreenTag = 'S'; + +@interface ScreenResponseController () +- (void)setFlashTimer; +- (void)flash:(NSTimer *)timer; +@end + +@implementation ScreenResponseController { + WALTClient *_client; + WALTLogger *_logger; + + NSTimer *_flashTimer; + NSOperationQueue *_readOperations; + + // Statistics + NSUInteger _initiatedFlashes; + NSUInteger _detectedFlashes; + + _Atomic NSTimeInterval _lastFlashTime; + NSMutableArray<NSNumber *> *_deltas; +} + +- (void)dealloc { + [_readOperations cancelAllOperations]; + [_flashTimer invalidate]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client; + _logger = [WALTLogger sessionLogger]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [_logger appendString:@"SCREENRESPONSE\n"]; + [self reset:nil]; +} + +- (IBAction)start:(id)sender { + [self reset:nil]; + + // Clear the screen trigger on the WALT. + NSError *error = nil; + if (![_client sendCommand:WALTSendLastScreenCommand error:&error]) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; + [alert show]; + return; + } + + WALTTrigger trigger = [_client readTriggerWithTimeout:kWALTReadTimeout]; + if (trigger.tag != kWALTScreenTag) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" + message:@"Failed to read last screen trigger." + delegate:nil + cancelButtonTitle:@"Dismiss" + otherButtonTitles:nil]; + [alert show]; + return; + } + + // Create a queue for work blocks to read WALT trigger responses. + _readOperations = [[NSOperationQueue alloc] init]; + _readOperations.maxConcurrentOperationCount = 1; + + // Start the flash timer and spawn a thread to check for responses. + [self setFlashTimer]; +} + +- (void)setFlashTimer { + _flashTimer = [NSTimer scheduledTimerWithTimeInterval:kFlashingInterval + target:self + selector:@selector(flash:) + userInfo:nil + repeats:NO]; +} + +- (IBAction)computeStatistics:(id)sender { + self.flasherView.hidden = YES; + self.responseLabel.hidden = NO; + + NSMutableString *results = [[NSMutableString alloc] init]; + for (NSNumber *delta in _deltas) { + [results appendFormat:@"%.3f s\n", delta.doubleValue]; + } + + [results appendFormat:@"Median: %.3f s\n", [_deltas medianValue].doubleValue]; + self.responseLabel.text = results; +} + +- (IBAction)reset:(id)sender { + _initiatedFlashes = 0; + _detectedFlashes = 0; + _deltas = [[NSMutableArray<NSNumber *> alloc] init]; + + [_readOperations cancelAllOperations]; + [_flashTimer invalidate]; + + self.flasherView.hidden = NO; + self.flasherView.backgroundColor = [UIColor whiteColor]; + self.responseLabel.hidden = YES; + + NSError *error = nil; + if (![_client syncClocksWithError:&error]) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; + [alert show]; + } + + [_logger appendString:@"RESET\n"]; +} + +- (void)flash:(NSTimer *)timer { + if (_initiatedFlashes == 0) { + // First flash. + // Turn on brightness change notifications. + NSError *error = nil; + if (![_client sendCommand:WALTScreenOnCommand error:&error]) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; + [alert show]; + return; + } + + NSData *response = [_client readResponseWithTimeout:kWALTReadTimeout]; + if (![_client checkResponse:response forCommand:WALTScreenOnCommand]) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" + message:@"Failed to start screen probe." + delegate:nil + cancelButtonTitle:@"Dismiss" + otherButtonTitles:nil]; + [alert show]; + return; + } + } + + if (_initiatedFlashes != kMaxFlashes) { + // Swap the background colour and record the time. + self.flasherView.backgroundColor = + ([self.flasherView.backgroundColor isEqual:[UIColor blackColor]] ? + [UIColor whiteColor] : + [UIColor blackColor]); + atomic_store(&_lastFlashTime, _client.currentTime); + ++_initiatedFlashes; + + // Queue an operation to read the trigger. + [_readOperations addOperationWithBlock:^{ + // NB: The timeout here should be much greater than the expected screen response time. + WALTTrigger response = [_client readTriggerWithTimeout:kWALTReadTimeout]; + if (response.tag == kWALTScreenTag) { + ++_detectedFlashes; + + // Record the delta between the trigger and the flash time. + NSTimeInterval lastFlash = atomic_load(&_lastFlashTime); + NSTimeInterval delta = response.t - lastFlash; + if (delta > 0) { // Sanity check + [_deltas addObject:[NSNumber numberWithDouble:delta]]; + [_logger appendFormat:@"O\t%f\n", delta]; + } else { + [_logger appendFormat:@"X\tbogus delta\t%f\t%f\n", lastFlash, response.t]; + } + + // Queue up another flash. + [self performSelectorOnMainThread:@selector(setFlashTimer) + withObject:nil + waitUntilDone:NO]; + } else { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" + message:@"Failed to read screen probe." + delegate:nil + cancelButtonTitle:@"Dismiss" + otherButtonTitles:nil]; + [alert show]; + } + }]; + } + + if (_initiatedFlashes == kMaxFlashes) { + // Queue an operation (after the read trigger above) to turn off brightness notifications. + [_readOperations addOperationWithBlock:^{ + [_client sendCommand:WALTScreenOffCommand error:nil]; + [_client readResponseWithTimeout:kWALTReadTimeout]; + [self performSelectorOnMainThread:@selector(computeStatistics:) + withObject:nil + waitUntilDone:NO]; + }]; + } +} +@end |