blob: c88236c53ff68edf5b8652cf620127246dbcc533 [file] [log] [blame]
/*
* 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