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