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