1*bf47c682SAndroid Build Coastguard Worker/* 2*bf47c682SAndroid Build Coastguard Worker * Copyright (C) 2016 The Android Open Source Project 3*bf47c682SAndroid Build Coastguard Worker * 4*bf47c682SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*bf47c682SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*bf47c682SAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*bf47c682SAndroid Build Coastguard Worker * 8*bf47c682SAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*bf47c682SAndroid Build Coastguard Worker * 10*bf47c682SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*bf47c682SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*bf47c682SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*bf47c682SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*bf47c682SAndroid Build Coastguard Worker * limitations under the License. 15*bf47c682SAndroid Build Coastguard Worker */ 16*bf47c682SAndroid Build Coastguard Worker 17*bf47c682SAndroid Build Coastguard Worker#import "ScreenResponseController.h" 18*bf47c682SAndroid Build Coastguard Worker 19*bf47c682SAndroid Build Coastguard Worker#include <stdatomic.h> 20*bf47c682SAndroid Build Coastguard Worker 21*bf47c682SAndroid Build Coastguard Worker#import "NSArray+Extensions.h" 22*bf47c682SAndroid Build Coastguard Worker#import "UIAlertView+Extensions.h" 23*bf47c682SAndroid Build Coastguard Worker#import "WALTAppDelegate.h" 24*bf47c682SAndroid Build Coastguard Worker#import "WALTClient.h" 25*bf47c682SAndroid Build Coastguard Worker#import "WALTLogger.h" 26*bf47c682SAndroid Build Coastguard Worker 27*bf47c682SAndroid Build Coastguard Workerstatic const NSUInteger kMaxFlashes = 20; // TODO(pquinn): Make this user-configurable. 28*bf47c682SAndroid Build Coastguard Workerstatic const NSTimeInterval kFlashingInterval = 0.1; 29*bf47c682SAndroid Build Coastguard Workerstatic const char kWALTScreenTag = 'S'; 30*bf47c682SAndroid Build Coastguard Worker 31*bf47c682SAndroid Build Coastguard Worker@interface ScreenResponseController () 32*bf47c682SAndroid Build Coastguard Worker- (void)setFlashTimer; 33*bf47c682SAndroid Build Coastguard Worker- (void)flash:(NSTimer *)timer; 34*bf47c682SAndroid Build Coastguard Worker@end 35*bf47c682SAndroid Build Coastguard Worker 36*bf47c682SAndroid Build Coastguard Worker@implementation ScreenResponseController { 37*bf47c682SAndroid Build Coastguard Worker WALTClient *_client; 38*bf47c682SAndroid Build Coastguard Worker WALTLogger *_logger; 39*bf47c682SAndroid Build Coastguard Worker 40*bf47c682SAndroid Build Coastguard Worker NSTimer *_flashTimer; 41*bf47c682SAndroid Build Coastguard Worker NSOperationQueue *_readOperations; 42*bf47c682SAndroid Build Coastguard Worker 43*bf47c682SAndroid Build Coastguard Worker // Statistics 44*bf47c682SAndroid Build Coastguard Worker NSUInteger _initiatedFlashes; 45*bf47c682SAndroid Build Coastguard Worker NSUInteger _detectedFlashes; 46*bf47c682SAndroid Build Coastguard Worker 47*bf47c682SAndroid Build Coastguard Worker _Atomic NSTimeInterval _lastFlashTime; 48*bf47c682SAndroid Build Coastguard Worker NSMutableArray<NSNumber *> *_deltas; 49*bf47c682SAndroid Build Coastguard Worker} 50*bf47c682SAndroid Build Coastguard Worker 51*bf47c682SAndroid Build Coastguard Worker- (void)dealloc { 52*bf47c682SAndroid Build Coastguard Worker [_readOperations cancelAllOperations]; 53*bf47c682SAndroid Build Coastguard Worker [_flashTimer invalidate]; 54*bf47c682SAndroid Build Coastguard Worker} 55*bf47c682SAndroid Build Coastguard Worker 56*bf47c682SAndroid Build Coastguard Worker- (void)viewDidLoad { 57*bf47c682SAndroid Build Coastguard Worker [super viewDidLoad]; 58*bf47c682SAndroid Build Coastguard Worker 59*bf47c682SAndroid Build Coastguard Worker _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client; 60*bf47c682SAndroid Build Coastguard Worker _logger = [WALTLogger sessionLogger]; 61*bf47c682SAndroid Build Coastguard Worker} 62*bf47c682SAndroid Build Coastguard Worker 63*bf47c682SAndroid Build Coastguard Worker- (void)viewWillAppear:(BOOL)animated { 64*bf47c682SAndroid Build Coastguard Worker [super viewWillAppear:animated]; 65*bf47c682SAndroid Build Coastguard Worker 66*bf47c682SAndroid Build Coastguard Worker [_logger appendString:@"SCREENRESPONSE\n"]; 67*bf47c682SAndroid Build Coastguard Worker [self reset:nil]; 68*bf47c682SAndroid Build Coastguard Worker} 69*bf47c682SAndroid Build Coastguard Worker 70*bf47c682SAndroid Build Coastguard Worker- (IBAction)start:(id)sender { 71*bf47c682SAndroid Build Coastguard Worker [self reset:nil]; 72*bf47c682SAndroid Build Coastguard Worker 73*bf47c682SAndroid Build Coastguard Worker // Clear the screen trigger on the WALT. 74*bf47c682SAndroid Build Coastguard Worker NSError *error = nil; 75*bf47c682SAndroid Build Coastguard Worker if (![_client sendCommand:WALTSendLastScreenCommand error:&error]) { 76*bf47c682SAndroid Build Coastguard Worker UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; 77*bf47c682SAndroid Build Coastguard Worker [alert show]; 78*bf47c682SAndroid Build Coastguard Worker return; 79*bf47c682SAndroid Build Coastguard Worker } 80*bf47c682SAndroid Build Coastguard Worker 81*bf47c682SAndroid Build Coastguard Worker WALTTrigger trigger = [_client readTriggerWithTimeout:kWALTReadTimeout]; 82*bf47c682SAndroid Build Coastguard Worker if (trigger.tag != kWALTScreenTag) { 83*bf47c682SAndroid Build Coastguard Worker UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" 84*bf47c682SAndroid Build Coastguard Worker message:@"Failed to read last screen trigger." 85*bf47c682SAndroid Build Coastguard Worker delegate:nil 86*bf47c682SAndroid Build Coastguard Worker cancelButtonTitle:@"Dismiss" 87*bf47c682SAndroid Build Coastguard Worker otherButtonTitles:nil]; 88*bf47c682SAndroid Build Coastguard Worker [alert show]; 89*bf47c682SAndroid Build Coastguard Worker return; 90*bf47c682SAndroid Build Coastguard Worker } 91*bf47c682SAndroid Build Coastguard Worker 92*bf47c682SAndroid Build Coastguard Worker // Create a queue for work blocks to read WALT trigger responses. 93*bf47c682SAndroid Build Coastguard Worker _readOperations = [[NSOperationQueue alloc] init]; 94*bf47c682SAndroid Build Coastguard Worker _readOperations.maxConcurrentOperationCount = 1; 95*bf47c682SAndroid Build Coastguard Worker 96*bf47c682SAndroid Build Coastguard Worker // Start the flash timer and spawn a thread to check for responses. 97*bf47c682SAndroid Build Coastguard Worker [self setFlashTimer]; 98*bf47c682SAndroid Build Coastguard Worker} 99*bf47c682SAndroid Build Coastguard Worker 100*bf47c682SAndroid Build Coastguard Worker- (void)setFlashTimer { 101*bf47c682SAndroid Build Coastguard Worker _flashTimer = [NSTimer scheduledTimerWithTimeInterval:kFlashingInterval 102*bf47c682SAndroid Build Coastguard Worker target:self 103*bf47c682SAndroid Build Coastguard Worker selector:@selector(flash:) 104*bf47c682SAndroid Build Coastguard Worker userInfo:nil 105*bf47c682SAndroid Build Coastguard Worker repeats:NO]; 106*bf47c682SAndroid Build Coastguard Worker} 107*bf47c682SAndroid Build Coastguard Worker 108*bf47c682SAndroid Build Coastguard Worker- (IBAction)computeStatistics:(id)sender { 109*bf47c682SAndroid Build Coastguard Worker self.flasherView.hidden = YES; 110*bf47c682SAndroid Build Coastguard Worker self.responseLabel.hidden = NO; 111*bf47c682SAndroid Build Coastguard Worker 112*bf47c682SAndroid Build Coastguard Worker NSMutableString *results = [[NSMutableString alloc] init]; 113*bf47c682SAndroid Build Coastguard Worker for (NSNumber *delta in _deltas) { 114*bf47c682SAndroid Build Coastguard Worker [results appendFormat:@"%.3f s\n", delta.doubleValue]; 115*bf47c682SAndroid Build Coastguard Worker } 116*bf47c682SAndroid Build Coastguard Worker 117*bf47c682SAndroid Build Coastguard Worker [results appendFormat:@"Median: %.3f s\n", [_deltas medianValue].doubleValue]; 118*bf47c682SAndroid Build Coastguard Worker self.responseLabel.text = results; 119*bf47c682SAndroid Build Coastguard Worker} 120*bf47c682SAndroid Build Coastguard Worker 121*bf47c682SAndroid Build Coastguard Worker- (IBAction)reset:(id)sender { 122*bf47c682SAndroid Build Coastguard Worker _initiatedFlashes = 0; 123*bf47c682SAndroid Build Coastguard Worker _detectedFlashes = 0; 124*bf47c682SAndroid Build Coastguard Worker _deltas = [[NSMutableArray<NSNumber *> alloc] init]; 125*bf47c682SAndroid Build Coastguard Worker 126*bf47c682SAndroid Build Coastguard Worker [_readOperations cancelAllOperations]; 127*bf47c682SAndroid Build Coastguard Worker [_flashTimer invalidate]; 128*bf47c682SAndroid Build Coastguard Worker 129*bf47c682SAndroid Build Coastguard Worker self.flasherView.hidden = NO; 130*bf47c682SAndroid Build Coastguard Worker self.flasherView.backgroundColor = [UIColor whiteColor]; 131*bf47c682SAndroid Build Coastguard Worker self.responseLabel.hidden = YES; 132*bf47c682SAndroid Build Coastguard Worker 133*bf47c682SAndroid Build Coastguard Worker NSError *error = nil; 134*bf47c682SAndroid Build Coastguard Worker if (![_client syncClocksWithError:&error]) { 135*bf47c682SAndroid Build Coastguard Worker UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; 136*bf47c682SAndroid Build Coastguard Worker [alert show]; 137*bf47c682SAndroid Build Coastguard Worker } 138*bf47c682SAndroid Build Coastguard Worker 139*bf47c682SAndroid Build Coastguard Worker [_logger appendString:@"RESET\n"]; 140*bf47c682SAndroid Build Coastguard Worker} 141*bf47c682SAndroid Build Coastguard Worker 142*bf47c682SAndroid Build Coastguard Worker- (void)flash:(NSTimer *)timer { 143*bf47c682SAndroid Build Coastguard Worker if (_initiatedFlashes == 0) { 144*bf47c682SAndroid Build Coastguard Worker // First flash. 145*bf47c682SAndroid Build Coastguard Worker // Turn on brightness change notifications. 146*bf47c682SAndroid Build Coastguard Worker NSError *error = nil; 147*bf47c682SAndroid Build Coastguard Worker if (![_client sendCommand:WALTScreenOnCommand error:&error]) { 148*bf47c682SAndroid Build Coastguard Worker UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; 149*bf47c682SAndroid Build Coastguard Worker [alert show]; 150*bf47c682SAndroid Build Coastguard Worker return; 151*bf47c682SAndroid Build Coastguard Worker } 152*bf47c682SAndroid Build Coastguard Worker 153*bf47c682SAndroid Build Coastguard Worker NSData *response = [_client readResponseWithTimeout:kWALTReadTimeout]; 154*bf47c682SAndroid Build Coastguard Worker if (![_client checkResponse:response forCommand:WALTScreenOnCommand]) { 155*bf47c682SAndroid Build Coastguard Worker UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" 156*bf47c682SAndroid Build Coastguard Worker message:@"Failed to start screen probe." 157*bf47c682SAndroid Build Coastguard Worker delegate:nil 158*bf47c682SAndroid Build Coastguard Worker cancelButtonTitle:@"Dismiss" 159*bf47c682SAndroid Build Coastguard Worker otherButtonTitles:nil]; 160*bf47c682SAndroid Build Coastguard Worker [alert show]; 161*bf47c682SAndroid Build Coastguard Worker return; 162*bf47c682SAndroid Build Coastguard Worker } 163*bf47c682SAndroid Build Coastguard Worker } 164*bf47c682SAndroid Build Coastguard Worker 165*bf47c682SAndroid Build Coastguard Worker if (_initiatedFlashes != kMaxFlashes) { 166*bf47c682SAndroid Build Coastguard Worker // Swap the background colour and record the time. 167*bf47c682SAndroid Build Coastguard Worker self.flasherView.backgroundColor = 168*bf47c682SAndroid Build Coastguard Worker ([self.flasherView.backgroundColor isEqual:[UIColor blackColor]] ? 169*bf47c682SAndroid Build Coastguard Worker [UIColor whiteColor] : 170*bf47c682SAndroid Build Coastguard Worker [UIColor blackColor]); 171*bf47c682SAndroid Build Coastguard Worker atomic_store(&_lastFlashTime, _client.currentTime); 172*bf47c682SAndroid Build Coastguard Worker ++_initiatedFlashes; 173*bf47c682SAndroid Build Coastguard Worker 174*bf47c682SAndroid Build Coastguard Worker // Queue an operation to read the trigger. 175*bf47c682SAndroid Build Coastguard Worker [_readOperations addOperationWithBlock:^{ 176*bf47c682SAndroid Build Coastguard Worker // NB: The timeout here should be much greater than the expected screen response time. 177*bf47c682SAndroid Build Coastguard Worker WALTTrigger response = [_client readTriggerWithTimeout:kWALTReadTimeout]; 178*bf47c682SAndroid Build Coastguard Worker if (response.tag == kWALTScreenTag) { 179*bf47c682SAndroid Build Coastguard Worker ++_detectedFlashes; 180*bf47c682SAndroid Build Coastguard Worker 181*bf47c682SAndroid Build Coastguard Worker // Record the delta between the trigger and the flash time. 182*bf47c682SAndroid Build Coastguard Worker NSTimeInterval lastFlash = atomic_load(&_lastFlashTime); 183*bf47c682SAndroid Build Coastguard Worker NSTimeInterval delta = response.t - lastFlash; 184*bf47c682SAndroid Build Coastguard Worker if (delta > 0) { // Sanity check 185*bf47c682SAndroid Build Coastguard Worker [_deltas addObject:[NSNumber numberWithDouble:delta]]; 186*bf47c682SAndroid Build Coastguard Worker [_logger appendFormat:@"O\t%f\n", delta]; 187*bf47c682SAndroid Build Coastguard Worker } else { 188*bf47c682SAndroid Build Coastguard Worker [_logger appendFormat:@"X\tbogus delta\t%f\t%f\n", lastFlash, response.t]; 189*bf47c682SAndroid Build Coastguard Worker } 190*bf47c682SAndroid Build Coastguard Worker 191*bf47c682SAndroid Build Coastguard Worker // Queue up another flash. 192*bf47c682SAndroid Build Coastguard Worker [self performSelectorOnMainThread:@selector(setFlashTimer) 193*bf47c682SAndroid Build Coastguard Worker withObject:nil 194*bf47c682SAndroid Build Coastguard Worker waitUntilDone:NO]; 195*bf47c682SAndroid Build Coastguard Worker } else { 196*bf47c682SAndroid Build Coastguard Worker UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Response Error" 197*bf47c682SAndroid Build Coastguard Worker message:@"Failed to read screen probe." 198*bf47c682SAndroid Build Coastguard Worker delegate:nil 199*bf47c682SAndroid Build Coastguard Worker cancelButtonTitle:@"Dismiss" 200*bf47c682SAndroid Build Coastguard Worker otherButtonTitles:nil]; 201*bf47c682SAndroid Build Coastguard Worker [alert show]; 202*bf47c682SAndroid Build Coastguard Worker } 203*bf47c682SAndroid Build Coastguard Worker }]; 204*bf47c682SAndroid Build Coastguard Worker } 205*bf47c682SAndroid Build Coastguard Worker 206*bf47c682SAndroid Build Coastguard Worker if (_initiatedFlashes == kMaxFlashes) { 207*bf47c682SAndroid Build Coastguard Worker // Queue an operation (after the read trigger above) to turn off brightness notifications. 208*bf47c682SAndroid Build Coastguard Worker [_readOperations addOperationWithBlock:^{ 209*bf47c682SAndroid Build Coastguard Worker [_client sendCommand:WALTScreenOffCommand error:nil]; 210*bf47c682SAndroid Build Coastguard Worker [_client readResponseWithTimeout:kWALTReadTimeout]; 211*bf47c682SAndroid Build Coastguard Worker [self performSelectorOnMainThread:@selector(computeStatistics:) 212*bf47c682SAndroid Build Coastguard Worker withObject:nil 213*bf47c682SAndroid Build Coastguard Worker waitUntilDone:NO]; 214*bf47c682SAndroid Build Coastguard Worker }]; 215*bf47c682SAndroid Build Coastguard Worker } 216*bf47c682SAndroid Build Coastguard Worker} 217*bf47c682SAndroid Build Coastguard Worker@end 218