Home | History | Annotate | Download | only in location
      1 /*
      2  * Copyright (C) 2012 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 package com.android.cts.verifier.location;
     18 
     19 import android.location.Location;
     20 import android.location.LocationListener;
     21 import android.location.LocationManager;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 
     26 import java.util.List;
     27 import java.util.ArrayList;
     28 
     29 public class LocationVerifier implements Handler.Callback {
     30 
     31     public static final String TAG = "CtsVerifierLocation";
     32 
     33     private static final int MSG_TIMEOUT = 1;
     34 
     35     /** Timing failures on first NUM_IGNORED_UPDATES updates are ignored. */
     36     private static final int NUM_IGNORED_UPDATES = 2;
     37 
     38     /* In active mode, the mean computed for the deltas should not be smaller
     39      * than mInterval * ACTIVE_MIN_MEAN_RATIO */
     40     private static final double ACTIVE_MIN_MEAN_RATIO = 0.75;
     41 
     42     /* In passive mode, the mean computed for the deltas should not be smaller
     43      * than mInterval * PASSIVE_MIN_MEAN_RATIO */
     44     private static final double PASSIVE_MIN_MEAN_RATIO = 0.1;
     45 
     46     /**
     47      * The standard deviation computed for the deltas should not be bigger
     48      * than mInterval * ALLOWED_STDEV_ERROR_RATIO
     49      * or MIN_STDEV_MS, whichever is higher.
     50      */
     51     private static final double ALLOWED_STDEV_ERROR_RATIO = 0.50;
     52     private static final long MIN_STDEV_MS = 1000;
     53 
     54     private final LocationManager mLocationManager;
     55     private final PassFailLog mCb;
     56     private final String mProvider;
     57     private final long mInterval;
     58     private final long mTimeout;
     59     private final Handler mHandler;
     60     private final int mRequestedUpdates;
     61     private final ActiveListener mActiveListener;
     62     private final PassiveListener mPassiveListener;
     63 
     64     private boolean isTestOutcomeSet = false;
     65     private long mLastActiveTimestamp = -1;
     66     private long mLastPassiveTimestamp = -1;
     67     private int mNumActiveUpdates = 0;
     68     private int mNumPassiveUpdates = 0;
     69     private boolean mIsMockProvider = false;
     70     private boolean mRunning = false;
     71     private boolean mActiveLocationArrive = false;
     72 
     73     private List<Long> mActiveDeltas = new ArrayList();
     74     private List<Long> mPassiveDeltas = new ArrayList();
     75 
     76     private class ActiveListener implements LocationListener {
     77         @Override
     78         public void onLocationChanged(Location location) {
     79             if (!mRunning) return;
     80 
     81             mActiveLocationArrive = true;
     82             mNumActiveUpdates++;
     83             scheduleTimeout();
     84 
     85             long timestamp = location.getTime();
     86             long delta = timestamp - mLastActiveTimestamp;
     87             mLastActiveTimestamp = timestamp;
     88 
     89             if (mNumActiveUpdates <= NUM_IGNORED_UPDATES ) {
     90                 mCb.log("(ignored) active " + mProvider + " update (" + delta + "ms)");
     91                 return;
     92             }
     93             if (location.isFromMockProvider() != mIsMockProvider) {
     94                 fail("location coming from \"" + mProvider +
     95                         "\" provider reports isFromMockProvider() to be " +
     96                         location.isFromMockProvider());
     97             }
     98 
     99             mActiveDeltas.add(delta);
    100             mCb.log("active " + mProvider + " update (" + delta + "ms)");
    101 
    102             if (mNumActiveUpdates >= mRequestedUpdates) {
    103                 assertMeanAndStdev(mProvider, mActiveDeltas, ACTIVE_MIN_MEAN_RATIO);
    104                 assertMeanAndStdev(LocationManager.PASSIVE_PROVIDER, mPassiveDeltas, PASSIVE_MIN_MEAN_RATIO);
    105                 pass();
    106             }
    107         }
    108 
    109         @Override
    110         public void onStatusChanged(String provider, int status, Bundle extras) { }
    111         @Override
    112         public void onProviderEnabled(String provider) { }
    113         @Override
    114         public void onProviderDisabled(String provider) { }
    115     }
    116 
    117     private void assertMeanAndStdev(String provider, List<Long> deltas, double minMeanRatio) {
    118         double mean = computeMean(deltas);
    119         double stdev = computeStdev(mean, deltas);
    120 
    121         double minMean = mInterval * minMeanRatio;
    122         if (mean < minMean) {
    123             fail(provider + " provider mean too small: " + mean
    124                  + " (min: " + minMean + ")");
    125             return;
    126         }
    127 
    128         double maxStdev = Math.max(MIN_STDEV_MS, mInterval * ALLOWED_STDEV_ERROR_RATIO);
    129         if (stdev > maxStdev) {
    130             fail (provider + " provider stdev too big: "
    131                   + stdev + " (max: " + maxStdev + ")");
    132             return;
    133         }
    134 
    135         mCb.log(provider + " provider mean: " + mean);
    136         mCb.log(provider + " provider stdev: " + stdev);
    137     }
    138 
    139     private double computeMean(List<Long> deltas) {
    140         long accumulator = 0;
    141         for (long d : deltas) {
    142             accumulator += d;
    143         }
    144         return accumulator / deltas.size();
    145     }
    146 
    147     private double computeStdev(double mean, List<Long> deltas) {
    148         double accumulator = 0;
    149         for (long d : deltas) {
    150             double diff = d - mean;
    151             accumulator += diff * diff;
    152         }
    153         return Math.sqrt(accumulator / (deltas.size() - 1));
    154     }
    155 
    156     private class PassiveListener implements LocationListener {
    157         @Override
    158         public void onLocationChanged(Location location) {
    159             if (!mRunning) return;
    160             if (!location.getProvider().equals(mProvider)) return;
    161 
    162             // When a test round start, passive listener shouldn't recevice location before active listener.
    163             // If this situation occurs, we treat this location as overdue location.
    164             // (The overdue location comes from previous test round, it occurs occasionally)
    165             // We have to skip it to prevent wrong calculation of time interval.
    166             if (!mActiveLocationArrive) {
    167                 mCb.log("ignoring passive " + mProvider + " update");
    168                 return;
    169             }
    170 
    171             mNumPassiveUpdates++;
    172             long timestamp = location.getTime();
    173             long delta = timestamp - mLastPassiveTimestamp;
    174             mLastPassiveTimestamp = timestamp;
    175 
    176             if (mNumPassiveUpdates <= NUM_IGNORED_UPDATES) {
    177                 mCb.log("(ignored) passive " + mProvider + " update (" + delta + "ms)");
    178                 return;
    179             }
    180             if (location.isFromMockProvider() != mIsMockProvider) {
    181                 fail("location coming from \"" + mProvider +
    182                         "\" provider reports isFromMockProvider() to be " +
    183                         location.isFromMockProvider());
    184             }
    185 
    186             mPassiveDeltas.add(delta);
    187             mCb.log("passive " + mProvider + " update (" + delta + "ms)");
    188         }
    189 
    190         @Override
    191         public void onStatusChanged(String provider, int status, Bundle extras) { }
    192         @Override
    193         public void onProviderEnabled(String provider) { }
    194         @Override
    195         public void onProviderDisabled(String provider) { }
    196     }
    197 
    198     public LocationVerifier(PassFailLog cb, LocationManager locationManager,
    199             String provider, long requestedInterval, int numUpdates, boolean isMockProvider) {
    200         mProvider = provider;
    201         mInterval = requestedInterval;
    202         // timeout at 60 seconds after interval time
    203         mTimeout = requestedInterval + 60 * 1000;
    204         mRequestedUpdates = numUpdates + NUM_IGNORED_UPDATES;
    205         mLocationManager = locationManager;
    206         mCb = cb;
    207         mHandler = new Handler(this);
    208         mActiveListener = new ActiveListener();
    209         mPassiveListener = new PassiveListener();
    210         mIsMockProvider = isMockProvider;
    211     }
    212 
    213     public void start() {
    214         mRunning = true;
    215         scheduleTimeout();
    216         mLastActiveTimestamp = System.currentTimeMillis();
    217         mLastPassiveTimestamp = mLastActiveTimestamp;
    218         mCb.log("enabling passive listener");
    219         mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0,
    220                 mPassiveListener);
    221         mCb.log("enabling " + mProvider + " (minTime=" + mInterval + "ms)");
    222         mLocationManager.requestLocationUpdates(mProvider, mInterval, 0,
    223                 mActiveListener);
    224     }
    225 
    226     public void stop() {
    227         mRunning = false;
    228         mCb.log("disabling " + mProvider);
    229         mLocationManager.removeUpdates(mActiveListener);
    230         mCb.log("disabling passive listener");
    231         mLocationManager.removeUpdates(mPassiveListener);
    232         mHandler.removeMessages(MSG_TIMEOUT);
    233     }
    234 
    235     private void pass() {
    236         if (!isTestOutcomeSet) {
    237             stop();
    238             mCb.pass();
    239             isTestOutcomeSet = true;
    240         }
    241     }
    242 
    243     private void fail(String s) {
    244         if (!isTestOutcomeSet) {
    245             stop();
    246             mCb.fail(s);
    247             isTestOutcomeSet = true;
    248         }
    249     }
    250 
    251     private void scheduleTimeout() {
    252         mHandler.removeMessages(MSG_TIMEOUT);
    253         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), mTimeout);
    254     }
    255 
    256     @Override
    257     public boolean handleMessage(Message msg) {
    258         if (!mRunning) return true;
    259         fail("timeout (" + mTimeout + "ms) waiting for " +
    260                 mProvider + " location change");
    261         return true;
    262     }
    263 }
    264