Home | History | Annotate | Download | only in WALT
      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 
     27 static const NSUInteger kMaxFlashes = 20;  // TODO(pquinn): Make this user-configurable.
     28 static const NSTimeInterval kFlashingInterval = 0.1;
     29 static 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