blob: f9091586b7fbb0d8ad282dfd1cdc20a40a656c40 [file] [log] [blame]
Nick Pelly6fa9ad42012-07-16 12:18:23 -07001/*
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
17package com.android.location.fused;
18
19import java.io.FileDescriptor;
20import java.io.PrintWriter;
21import java.util.HashMap;
22
Victoria Lease779b7742012-10-31 15:54:05 -070023import com.android.location.provider.LocationProviderBase;
Laurent Tu7ab7f532012-10-30 14:54:02 -070024import com.android.location.provider.LocationRequestUnbundled;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070025import com.android.location.provider.ProviderRequestUnbundled;
26
Nick Pelly6fa9ad42012-07-16 12:18:23 -070027import android.content.Context;
28import android.location.Location;
29import android.location.LocationListener;
30import android.location.LocationManager;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070031import android.os.Bundle;
32import android.os.Looper;
Victoria Lease779b7742012-10-31 15:54:05 -070033import android.os.Parcelable;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070034import android.os.SystemClock;
35import android.os.WorkSource;
36import android.util.Log;
37
38public class FusionEngine implements LocationListener {
39 public interface Callback {
40 public void reportLocation(Location location);
41 }
42
43 private static final String TAG = "FusedLocation";
44 private static final String NETWORK = LocationManager.NETWORK_PROVIDER;
45 private static final String GPS = LocationManager.GPS_PROVIDER;
Victoria Lease779b7742012-10-31 15:54:05 -070046 private static final String FUSED = LocationProviderBase.FUSED_PROVIDER;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070047
Victoria Lease0fdfa6b2012-10-12 14:52:34 -070048 public static final long SWITCH_ON_FRESHNESS_CLIFF_NS = 11 * 1000000000; // 11 seconds
Nick Pelly6fa9ad42012-07-16 12:18:23 -070049
50 private final Context mContext;
51 private final LocationManager mLocationManager;
52 private final Looper mLooper;
53
54 // all fields are only used on mLooper thread. except for in dump() which is not thread-safe
55 private Callback mCallback;
56 private Location mFusedLocation;
57 private Location mGpsLocation;
58 private Location mNetworkLocation;
Nick Pelly6fa9ad42012-07-16 12:18:23 -070059
60 private boolean mEnabled;
61 private ProviderRequestUnbundled mRequest;
62
63 private final HashMap<String, ProviderStats> mStats = new HashMap<String, ProviderStats>();
64
65 public FusionEngine(Context context, Looper looper) {
66 mContext = context;
67 mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
68 mNetworkLocation = new Location("");
69 mNetworkLocation.setAccuracy(Float.MAX_VALUE);
70 mGpsLocation = new Location("");
71 mGpsLocation.setAccuracy(Float.MAX_VALUE);
72 mLooper = looper;
73
74 mStats.put(GPS, new ProviderStats());
75 mStats.get(GPS).available = mLocationManager.isProviderEnabled(GPS);
76 mStats.put(NETWORK, new ProviderStats());
77 mStats.get(NETWORK).available = mLocationManager.isProviderEnabled(NETWORK);
Victoria Lease779b7742012-10-31 15:54:05 -070078
Nick Pelly6fa9ad42012-07-16 12:18:23 -070079 }
80
81 public void init(Callback callback) {
82 Log.i(TAG, "engine started (" + mContext.getPackageName() + ")");
83 mCallback = callback;
84 }
85
86 /**
87 * Called to stop doing any work, and release all resources
88 * This can happen when a better fusion engine is installed
89 * in a different package, and this one is no longer needed.
90 * Called on mLooper thread
91 */
92 public void deinit() {
93 mRequest = null;
94 disable();
95 Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")");
96 }
97
Nick Pelly6fa9ad42012-07-16 12:18:23 -070098 /** Called on mLooper thread */
99 public void enable() {
100 mEnabled = true;
101 updateRequirements();
102 }
103
104 /** Called on mLooper thread */
105 public void disable() {
106 mEnabled = false;
107 updateRequirements();
108 }
109
110 /** Called on mLooper thread */
Nick Pelly08ca1042012-08-10 15:47:53 -0700111 public void setRequest(ProviderRequestUnbundled request, WorkSource source) {
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700112 mRequest = request;
Nick Pelly08ca1042012-08-10 15:47:53 -0700113 mEnabled = request.getReportLocation();
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700114 updateRequirements();
115 }
116
117 private static class ProviderStats {
118 public boolean available;
119 public boolean requested;
120 public long requestTime;
121 public long minTime;
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700122 @Override
123 public String toString() {
124 StringBuilder s = new StringBuilder();
125 s.append(available ? "AVAILABLE" : "UNAVAILABLE");
126 s.append(requested ? " REQUESTED" : " ---");
127 return s.toString();
128 }
129 }
130
131 private void enableProvider(String name, long minTime) {
132 ProviderStats stats = mStats.get(name);
133
134 if (!stats.requested) {
135 stats.requestTime = SystemClock.elapsedRealtime();
136 stats.requested = true;
137 stats.minTime = minTime;
138 mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
139 } else if (stats.minTime != minTime) {
140 stats.minTime = minTime;
141 mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
142 }
143 }
144
145 private void disableProvider(String name) {
146 ProviderStats stats = mStats.get(name);
147
148 if (stats.requested) {
149 stats.requested = false;
150 mLocationManager.removeUpdates(this); //TODO GLOBAL
151 }
152 }
153
154 private void updateRequirements() {
155 if (mEnabled == false || mRequest == null) {
156 mRequest = null;
157 disableProvider(NETWORK);
158 disableProvider(GPS);
159 return;
160 }
161
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700162 long networkInterval = Long.MAX_VALUE;
163 long gpsInterval = Long.MAX_VALUE;
Laurent Tu7ab7f532012-10-30 14:54:02 -0700164 for (LocationRequestUnbundled request : mRequest.getLocationRequests()) {
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700165 switch (request.getQuality()) {
Laurent Tu7ab7f532012-10-30 14:54:02 -0700166 case LocationRequestUnbundled.ACCURACY_FINE:
167 case LocationRequestUnbundled.POWER_HIGH:
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700168 if (request.getInterval() < gpsInterval) {
169 gpsInterval = request.getInterval();
170 }
171 if (request.getInterval() < networkInterval) {
172 networkInterval = request.getInterval();
173 }
174 break;
Laurent Tu7ab7f532012-10-30 14:54:02 -0700175 case LocationRequestUnbundled.ACCURACY_BLOCK:
176 case LocationRequestUnbundled.ACCURACY_CITY:
177 case LocationRequestUnbundled.POWER_LOW:
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700178 if (request.getInterval() < networkInterval) {
179 networkInterval = request.getInterval();
180 }
181 break;
182 }
183 }
184
185 if (gpsInterval < Long.MAX_VALUE) {
186 enableProvider(GPS, gpsInterval);
187 } else {
188 disableProvider(GPS);
189 }
190 if (networkInterval < Long.MAX_VALUE) {
191 enableProvider(NETWORK, networkInterval);
192 } else {
193 disableProvider(NETWORK);
194 }
195 }
196
Victoria Lease0fdfa6b2012-10-12 14:52:34 -0700197 /**
198 * Test whether one location (a) is better to use than another (b).
199 */
200 private static boolean isBetterThan(Location locationA, Location locationB) {
201 if (locationA == null) {
202 return false;
203 }
204 if (locationB == null) {
205 return true;
206 }
207 // A provider is better if the reading is sufficiently newer. Heading
208 // underground can cause GPS to stop reporting fixes. In this case it's
209 // appropriate to revert to cell, even when its accuracy is less.
210 if (locationA.getElapsedRealtimeNanos() > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) {
211 return true;
212 }
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700213
Victoria Lease0fdfa6b2012-10-12 14:52:34 -0700214 // A provider is better if it has better accuracy. Assuming both readings
215 // are fresh (and by that accurate), choose the one with the smaller
216 // accuracy circle.
217 if (!locationA.hasAccuracy()) {
218 return false;
219 }
220 if (!locationB.hasAccuracy()) {
221 return true;
222 }
223 return locationA.getAccuracy() < locationB.getAccuracy();
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700224 }
225
226 private void updateFusedLocation() {
Victoria Lease0fdfa6b2012-10-12 14:52:34 -0700227 // may the best location win!
228 if (isBetterThan(mGpsLocation, mNetworkLocation)) {
229 mFusedLocation = new Location(mGpsLocation);
230 } else {
231 mFusedLocation = new Location(mNetworkLocation);
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700232 }
Victoria Lease779b7742012-10-31 15:54:05 -0700233 mFusedLocation.setProvider(FUSED);
Victoria Lease09016ab2012-09-16 12:33:15 -0700234 if (mNetworkLocation != null) {
Victoria Lease779b7742012-10-31 15:54:05 -0700235 // copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation
236 Bundle srcExtras = mNetworkLocation.getExtras();
237 if (srcExtras != null) {
238 Parcelable srcParcelable =
239 srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION);
240 if (srcParcelable instanceof Location) {
241 Bundle dstExtras = mFusedLocation.getExtras();
242 if (dstExtras == null) {
243 dstExtras = new Bundle();
244 mFusedLocation.setExtras(dstExtras);
245 }
246 dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION,
247 (Location) srcParcelable);
248 }
249 }
Victoria Lease09016ab2012-09-16 12:33:15 -0700250 }
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700251
Laurent Tue886adb2012-12-04 10:58:43 -0800252 if (mCallback != null) {
253 mCallback.reportLocation(mFusedLocation);
254 } else {
255 Log.w(TAG, "Location updates received while fusion engine not started");
256 }
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700257 }
258
259 /** Called on mLooper thread */
260 @Override
261 public void onLocationChanged(Location location) {
262 if (GPS.equals(location.getProvider())) {
263 mGpsLocation = location;
264 updateFusedLocation();
265 } else if (NETWORK.equals(location.getProvider())) {
266 mNetworkLocation = location;
267 updateFusedLocation();
268 }
269 }
270
271 /** Called on mLooper thread */
272 @Override
273 public void onStatusChanged(String provider, int status, Bundle extras) { }
274
275 /** Called on mLooper thread */
276 @Override
277 public void onProviderEnabled(String provider) {
278 ProviderStats stats = mStats.get(provider);
279 if (stats == null) return;
280
281 stats.available = true;
282 }
283
284 /** Called on mLooper thread */
285 @Override
286 public void onProviderDisabled(String provider) {
287 ProviderStats stats = mStats.get(provider);
288 if (stats == null) return;
289
290 stats.available = false;
291 }
292
293 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
294 StringBuilder s = new StringBuilder();
295 s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n');
296 s.append("fused=").append(mFusedLocation).append('\n');
Victoria Lease0fdfa6b2012-10-12 14:52:34 -0700297 s.append(String.format("gps %s\n", mGpsLocation));
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700298 s.append(" ").append(mStats.get(GPS)).append('\n');
Victoria Lease0fdfa6b2012-10-12 14:52:34 -0700299 s.append(String.format("net %s\n", mNetworkLocation));
Nick Pelly6fa9ad42012-07-16 12:18:23 -0700300 s.append(" ").append(mStats.get(NETWORK)).append('\n');
301 pw.append(s);
302 }
303}