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 "TapLatencyController.h" 18 19#import "NSArray+Extensions.h" 20#import "UIAlertView+Extensions.h" 21#import "WALTAppDelegate.h" 22#import "WALTClient.h" 23#import "WALTLogger.h" 24#import "WALTTouch.h" 25 26@interface TapLatencyController () 27- (void)updateCountDisplay; 28- (void)processEvent:(UIEvent *)event; 29- (void)appendToLogView:(NSString *)string; 30- (void)computeStatisticsForPhase:(UITouchPhase)phase; 31@end 32 33@implementation TapLatencyController { 34 WALTClient *_client; 35 WALTLogger *_logger; 36 37 // Statistics 38 unsigned int _downCount; 39 unsigned int _downCountRecorded; 40 unsigned int _upCount; 41 unsigned int _upCountRecorded; 42 43 NSMutableArray<WALTTouch *> *_touches; 44} 45 46- (void)viewDidLoad { 47 [super viewDidLoad]; 48 49 self.logView.selectable = YES; 50 self.logView.text = [NSString string]; 51 self.logView.selectable = NO; 52 53 _logger = [WALTLogger sessionLogger]; 54 _client = ((WALTAppDelegate *)[UIApplication sharedApplication].delegate).client; 55} 56 57- (void)viewWillAppear:(BOOL)animated { 58 [super viewWillAppear:animated]; 59 60 [_logger appendString:@"TAPLATENCY\n"]; 61 [self reset:nil]; 62} 63 64- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 65 [self processEvent:event]; 66 [self updateCountDisplay]; 67} 68 69- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 70 [self processEvent:event]; 71 [self updateCountDisplay]; 72} 73 74- (void)updateCountDisplay { 75 NSString *counts = [NSString stringWithFormat:@"N ↓%u (%u) ↑%u (%u)", 76 _downCountRecorded, _downCount, _upCountRecorded, _upCount]; 77 self.countLabel.text = counts; 78} 79 80- (void)processEvent:(UIEvent *)event { 81 // TODO(pquinn): Pick first/last coalesced touch? 82 83 NSTimeInterval kernelTime = event.timestamp; 84 NSTimeInterval callbackTime = _client.currentTime; 85 86 NSError *error = nil; 87 NSTimeInterval physicalTime = [_client lastShockTimeWithError:&error]; 88 if (physicalTime == -1) { 89 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; 90 [alert show]; 91 return; 92 } 93 94 WALTTouch *touch = [[WALTTouch alloc] initWithEvent:event]; 95 touch.callbackTime = callbackTime; 96 touch.physicalTime = physicalTime; 97 98 NSString *actionString = nil; 99 if (touch.phase == UITouchPhaseBegan) { 100 _downCount += 1; 101 actionString = @"ACTION_DOWN"; 102 } else { 103 _upCount += 1; 104 actionString = @"ACTION_UP"; 105 } 106 107 if (physicalTime == 0) { 108 [_logger appendFormat:@"%@\tX\tno shock\n", actionString]; 109 [self appendToLogView:[NSString stringWithFormat:@"%@: No shock detected\n", actionString]]; 110 return; 111 } 112 113 NSTimeInterval physicalToKernel = kernelTime - physicalTime; 114 NSTimeInterval kernelToCallback = callbackTime - kernelTime; 115 116 if (physicalToKernel < 0 || physicalToKernel > 0.2) { 117 [_logger appendFormat:@"%@\tX\tbogus kernelTime\t%f\n", actionString, physicalToKernel]; 118 [self appendToLogView: 119 [NSString stringWithFormat:@"%@: Bogus P → K: %.3f s\n", actionString, physicalToKernel]]; 120 return; 121 } 122 123 [_logger appendFormat:@"%@\tO\t%f\t%f\t%f\n", 124 actionString, _client.baseTime, physicalToKernel, kernelToCallback]; 125 126 [self appendToLogView: 127 [NSString stringWithFormat:@"%@: P → K: %.3f s; K → C: %.3f s\n", 128 actionString, physicalToKernel, kernelToCallback]]; 129 130 [_touches addObject:touch]; 131 if (touch.phase == UITouchPhaseBegan) { 132 _downCountRecorded += 1; 133 } else { 134 _upCountRecorded += 1; 135 } 136} 137 138- (IBAction)reset:(id)sender { 139 _downCount = 0; 140 _downCountRecorded = 0; 141 _upCount = 0; 142 _upCountRecorded = 0; 143 [self updateCountDisplay]; 144 145 _touches = [[NSMutableArray<WALTTouch *> alloc] init]; 146 147 NSError *error = nil; 148 if (![_client syncClocksWithError:&error]) { 149 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"WALT Connection Error" error:error]; 150 [alert show]; 151 } 152 153 [_logger appendString:@"RESET\n"]; 154 [self appendToLogView:@"===========================================\n"]; 155} 156 157- (IBAction)computeStatistics:(id)sender { 158 [self appendToLogView:@"-------------------------------------------\n"]; 159 [self appendToLogView:@"Medians:\n"]; 160 [self computeStatisticsForPhase:UITouchPhaseBegan]; 161 [self computeStatisticsForPhase:UITouchPhaseEnded]; 162 163 [self reset:sender]; 164} 165 166- (void)computeStatisticsForPhase:(UITouchPhase)phase { 167 NSMutableArray<NSNumber *> *p2k = [[NSMutableArray<NSNumber *> alloc] init]; 168 NSMutableArray<NSNumber *> *k2c = [[NSMutableArray<NSNumber *> alloc] init]; 169 170 for (WALTTouch *touch in _touches) { 171 if (touch.phase != phase) { 172 continue; 173 } 174 175 [p2k addObject:[NSNumber numberWithDouble:touch.kernelTime - touch.physicalTime]]; 176 [k2c addObject:[NSNumber numberWithDouble:touch.callbackTime - touch.kernelTime]]; 177 } 178 179 NSNumber *p2kMedian = [p2k medianValue]; 180 NSNumber *k2cMedian = [k2c medianValue]; 181 182 NSString *actionString = (phase == UITouchPhaseBegan ? @"ACTION_DOWN" : @"ACTION_UP"); 183 [self appendToLogView: 184 [NSString stringWithFormat:@"%@: P → K: %.3f s; K → C: %.3f s\n", 185 actionString, p2kMedian.doubleValue, k2cMedian.doubleValue]]; 186} 187 188- (void)appendToLogView:(NSString*)string { 189 self.logView.selectable = YES; 190 self.logView.text = [self.logView.text stringByAppendingString:string]; 191 [self.logView scrollRangeToVisible:NSMakeRange(self.logView.text.length - 2, 1)]; 192 self.logView.selectable = NO; 193} 194@end 195