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