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 onLocationChanged(Location location)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 onStatusChanged(String provider, int status, Bundle extras)110 public void onStatusChanged(String provider, int status, Bundle extras) { } 111 @Override onProviderEnabled(String provider)112 public void onProviderEnabled(String provider) { } 113 @Override onProviderDisabled(String provider)114 public void onProviderDisabled(String provider) { } 115 } 116 assertMeanAndStdev(String provider, List<Long> deltas, double minMeanRatio)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 computeMean(List<Long> deltas)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 computeStdev(double mean, List<Long> deltas)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 onLocationChanged(Location location)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 onStatusChanged(String provider, int status, Bundle extras)191 public void onStatusChanged(String provider, int status, Bundle extras) { } 192 @Override onProviderEnabled(String provider)193 public void onProviderEnabled(String provider) { } 194 @Override onProviderDisabled(String provider)195 public void onProviderDisabled(String provider) { } 196 } 197 LocationVerifier(PassFailLog cb, LocationManager locationManager, String provider, long requestedInterval, int numUpdates, boolean isMockProvider)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 start()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 stop()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 pass()235 private void pass() { 236 if (!isTestOutcomeSet) { 237 stop(); 238 mCb.pass(); 239 isTestOutcomeSet = true; 240 } 241 } 242 fail(String s)243 private void fail(String s) { 244 if (!isTestOutcomeSet) { 245 stop(); 246 mCb.fail(s); 247 isTestOutcomeSet = true; 248 } 249 } 250 scheduleTimeout()251 private void scheduleTimeout() { 252 mHandler.removeMessages(MSG_TIMEOUT); 253 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), mTimeout); 254 } 255 256 @Override handleMessage(Message msg)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