FusionEngine.java revision 09016ab4dd056a16809419d612cb865a14980880
16fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly/*
26fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * Copyright (C) 2012 The Android Open Source Project
36fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly *
46fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * Licensed under the Apache License, Version 2.0 (the "License");
56fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * you may not use this file except in compliance with the License.
66fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * You may obtain a copy of the License at
76fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly *
86fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly *      http://www.apache.org/licenses/LICENSE-2.0
96fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly *
106fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * Unless required by applicable law or agreed to in writing, software
116fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * distributed under the License is distributed on an "AS IS" BASIS,
126fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * See the License for the specific language governing permissions and
146fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly * limitations under the License.
156fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly */
166fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
176fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellypackage com.android.location.fused;
186fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
196fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport java.io.FileDescriptor;
206fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport java.io.PrintWriter;
216fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport java.util.HashMap;
226fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
236fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport com.android.location.provider.ProviderRequestUnbundled;
246fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
256fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.content.Context;
266fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.location.Location;
276fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.location.LocationListener;
286fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.location.LocationManager;
296fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.location.LocationRequest;
306fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.os.Bundle;
316fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.os.Looper;
326fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.os.SystemClock;
336fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.os.WorkSource;
346fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellyimport android.util.Log;
356fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
366fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pellypublic class FusionEngine implements LocationListener {
376fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public interface Callback {
386fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        public void reportLocation(Location location);
396fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
406fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
416fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final String TAG = "FusedLocation";
426fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final String NETWORK = LocationManager.NETWORK_PROVIDER;
436fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final String GPS = LocationManager.GPS_PROVIDER;
446fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
456fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    // threshold below which a location is considered stale enough
466fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    // that we shouldn't use its bearing, altitude, speed etc
476fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final double WEIGHT_THRESHOLD = 0.5;
486fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    // accuracy in meters at which a Location's weight is halved (compared to 0 accuracy)
496fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final double ACCURACY_HALFLIFE_M = 20.0;
506fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    // age in seconds at which a Location's weight is halved (compared to 0 age)
516fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final double AGE_HALFLIFE_S = 60.0;
526fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
536fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final double ACCURACY_DECAY_CONSTANT_M = Math.log(2) / ACCURACY_HALFLIFE_M;
546fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static final double AGE_DECAY_CONSTANT_S = Math.log(2) / AGE_HALFLIFE_S;
556fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
566fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private final Context mContext;
576fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private final LocationManager mLocationManager;
586fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private final Looper mLooper;
596fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
606fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    // all fields are only used on mLooper thread. except for in dump() which is not thread-safe
616fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private Callback mCallback;
626fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private Location mFusedLocation;
636fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private Location mGpsLocation;
646fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private Location mNetworkLocation;
656fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private double mNetworkWeight;
666fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private double mGpsWeight;
676fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
686fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private boolean mEnabled;
696fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private ProviderRequestUnbundled mRequest;
706fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
716fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private final HashMap<String, ProviderStats> mStats = new HashMap<String, ProviderStats>();
726fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
736fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public FusionEngine(Context context, Looper looper) {
746fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mContext = context;
756fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
766fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mNetworkLocation = new Location("");
776fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mNetworkLocation.setAccuracy(Float.MAX_VALUE);
786fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mGpsLocation = new Location("");
796fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mGpsLocation.setAccuracy(Float.MAX_VALUE);
806fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mLooper = looper;
816fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
826fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mStats.put(GPS, new ProviderStats());
836fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mStats.get(GPS).available = mLocationManager.isProviderEnabled(GPS);
846fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mStats.put(NETWORK, new ProviderStats());
856fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mStats.get(NETWORK).available = mLocationManager.isProviderEnabled(NETWORK);
866fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
876fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
886fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void init(Callback callback) {
896fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        Log.i(TAG, "engine started (" + mContext.getPackageName() + ")");
906fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mCallback = callback;
916fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
926fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
936fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /**
946fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly     * Called to stop doing any work, and release all resources
956fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly     * This can happen when a better fusion engine is installed
966fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly     * in a different package, and this one is no longer needed.
976fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly     * Called on mLooper thread
986fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly     */
996fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void deinit() {
1006fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mRequest = null;
1016fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        disable();
1026fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")");
1036fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1046fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1056fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private boolean isAvailable() {
1066fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        return mStats.get(GPS).available || mStats.get(NETWORK).available;
1076fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1086fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1096fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /** Called on mLooper thread */
1106fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void enable() {
1116fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mEnabled = true;
1126fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        updateRequirements();
1136fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1146fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1156fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /** Called on mLooper thread */
1166fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void disable() {
1176fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mEnabled = false;
1186fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        updateRequirements();
1196fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1206fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1216fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /** Called on mLooper thread */
12208ca1046fe4f1890f91241f8d082a024ef6cfd93Nick Pelly    public void setRequest(ProviderRequestUnbundled request, WorkSource source) {
1236fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mRequest = request;
12408ca1046fe4f1890f91241f8d082a024ef6cfd93Nick Pelly        mEnabled = request.getReportLocation();
1256fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        updateRequirements();
1266fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1276fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1286fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static class ProviderStats {
1296fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        public boolean available;
1306fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        public boolean requested;
1316fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        public long requestTime;
1326fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        public long minTime;
1336fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        public long lastRequestTtff;
1346fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        @Override
1356fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        public String toString() {
1366fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            StringBuilder s = new StringBuilder();
1376fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            s.append(available ? "AVAILABLE" : "UNAVAILABLE");
1386fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            s.append(requested ? " REQUESTED" : " ---");
1396fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            return s.toString();
1406fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
1416fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1426fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1436fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private void enableProvider(String name, long minTime) {
1446fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        ProviderStats stats = mStats.get(name);
1456fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1466fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (!stats.requested) {
1476fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            stats.requestTime = SystemClock.elapsedRealtime();
1486fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            stats.requested = true;
1496fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            stats.minTime = minTime;
1506fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
1516fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (stats.minTime != minTime) {
1526fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            stats.minTime = minTime;
1536fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
1546fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
1556fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1566fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1576fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private void disableProvider(String name) {
1586fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        ProviderStats stats = mStats.get(name);
1596fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1606fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (stats.requested) {
1616fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            stats.requested = false;
1626fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            mLocationManager.removeUpdates(this);  //TODO GLOBAL
1636fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
1646fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
1656fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1666fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private void updateRequirements() {
1676fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (mEnabled == false || mRequest == null) {
1686fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            mRequest = null;
1696fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            disableProvider(NETWORK);
1706fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            disableProvider(GPS);
1716fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            return;
1726fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
1736fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1746fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        ProviderStats gpsStats = mStats.get(GPS);
1756fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        ProviderStats networkStats = mStats.get(NETWORK);
1766fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
1776fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        long networkInterval = Long.MAX_VALUE;
1786fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        long gpsInterval = Long.MAX_VALUE;
1796fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        for (LocationRequest request : mRequest.getLocationRequests()) {
1806fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            switch (request.getQuality()) {
1816fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                case LocationRequest.ACCURACY_FINE:
1826fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                case LocationRequest.POWER_HIGH:
1836fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    if (request.getInterval() < gpsInterval) {
1846fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                        gpsInterval = request.getInterval();
1856fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    }
1866fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    if (request.getInterval() < networkInterval) {
1876fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                        networkInterval = request.getInterval();
1886fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    }
1896fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    break;
1906fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                case LocationRequest.ACCURACY_BLOCK:
1916fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                case LocationRequest.ACCURACY_CITY:
1926fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                case LocationRequest.POWER_LOW:
1936fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    if (request.getInterval() < networkInterval) {
1946fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                        networkInterval = request.getInterval();
1956fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    }
1966fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    break;
1976fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            }
1986fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
1996fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2006fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (gpsInterval < Long.MAX_VALUE) {
2016fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            enableProvider(GPS, gpsInterval);
2026fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else {
2036fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            disableProvider(GPS);
2046fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
2056fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (networkInterval < Long.MAX_VALUE) {
2066fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            enableProvider(NETWORK, networkInterval);
2076fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else {
2086fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            disableProvider(NETWORK);
2096fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
2106fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
2116fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2126fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static double weighAccuracy(Location loc) {
2136fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        double accuracy = loc.getAccuracy();
2146fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        return Math.exp(-accuracy * ACCURACY_DECAY_CONSTANT_M);
2156fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
2166fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2176fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private static double weighAge(Location loc) {
2186fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        long ageSeconds = SystemClock.elapsedRealtimeNano() - loc.getElapsedRealtimeNano();
2196fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        ageSeconds /= 1000000000L;
2206fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (ageSeconds < 0) ageSeconds = 0;
2216fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        return Math.exp(-ageSeconds * AGE_DECAY_CONSTANT_S);
2226fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
2236fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2246fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private double weigh(double gps, double network) {
2256fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        return (gps * mGpsWeight) + (network * mNetworkWeight);
2266fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
2276fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2286fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private double weigh(double gps, double network, double wrapMin, double wrapMax) {
2296fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // apply aliasing
2306fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        double wrapWidth = wrapMax - wrapMin;
2316fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (gps - network > wrapWidth / 2) network += wrapWidth;
2326fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        else if (network - gps > wrapWidth / 2) gps += wrapWidth;
2336fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2346fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        double result = weigh(gps, network);
2356fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2366fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // remove aliasing
2376fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (result > wrapMax) result -= wrapWidth;
2386fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        return result;
2396fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
2406fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2416fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    private void updateFusedLocation() {
2426fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // naive fusion
2436fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mNetworkWeight = weighAccuracy(mNetworkLocation) * weighAge(mNetworkLocation);
2446fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mGpsWeight = weighAccuracy(mGpsLocation) * weighAge(mGpsLocation);
2456fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // scale mNetworkWeight and mGpsWeight so that they add to 1
2466fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        double totalWeight = mNetworkWeight + mGpsWeight;
2476fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mNetworkWeight /= totalWeight;
2486fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mGpsWeight /= totalWeight;
2496fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2506fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        Location fused = new Location(LocationManager.FUSED_PROVIDER);
2516fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // fuse lat/long
2526fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // assumes the two locations are close enough that earth curvature doesn't matter
2536fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        fused.setLatitude(weigh(mGpsLocation.getLatitude(), mNetworkLocation.getLatitude()));
2546fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        fused.setLongitude(weigh(mGpsLocation.getLongitude(), mNetworkLocation.getLongitude(),
2556fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                -180.0, 180.0));
2566fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2576fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // fused accuracy
2586fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        //TODO: use some real math instead of this crude fusion
2596fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // one suggestion is to fuse in a quadratic manner, eg
2606fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // sqrt(weigh(gpsAcc^2, netAcc^2)).
2616fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // another direction to explore is to consider the difference in the 2
2626fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // locations. If the component locations overlap, the fused accuracy is
2636fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // better than the component accuracies. If they are far apart,
2646fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // the fused accuracy is much worse.
2656fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        fused.setAccuracy((float)weigh(mGpsLocation.getAccuracy(), mNetworkLocation.getAccuracy()));
2666fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2676fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // fused time - now
2686fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        fused.setTime(System.currentTimeMillis());
2696fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        fused.setElapsedRealtimeNano(SystemClock.elapsedRealtimeNano());
2706fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2716fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // fuse altitude
2726fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (mGpsLocation.hasAltitude() && !mNetworkLocation.hasAltitude() &&
2736fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                mGpsWeight > WEIGHT_THRESHOLD) {
2746fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setAltitude(mGpsLocation.getAltitude());   // use GPS
2756fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (!mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude() &&
2766fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                mNetworkWeight > WEIGHT_THRESHOLD) {
2776fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setAltitude(mNetworkLocation.getAltitude());   // use Network
2786fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude()) {
2796fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setAltitude(weigh(mGpsLocation.getAltitude(), mNetworkLocation.getAltitude()));
2806fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
2816fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2826fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // fuse speed
2836fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (mGpsLocation.hasSpeed() && !mNetworkLocation.hasSpeed() &&
2846fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                mGpsWeight > WEIGHT_THRESHOLD) {
2856fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setSpeed(mGpsLocation.getSpeed());   // use GPS if its not too old
2866fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (!mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed() &&
2876fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                mNetworkWeight > WEIGHT_THRESHOLD) {
2886fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setSpeed(mNetworkLocation.getSpeed());   // use Network
2896fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed()) {
2906fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setSpeed((float)weigh(mGpsLocation.getSpeed(), mNetworkLocation.getSpeed()));
2916fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
2926fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
2936fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        // fuse bearing
2946fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (mGpsLocation.hasBearing() && !mNetworkLocation.hasBearing() &&
2956fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                mGpsWeight > WEIGHT_THRESHOLD) {
2966fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setBearing(mGpsLocation.getBearing());   // use GPS if its not too old
2976fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (!mGpsLocation.hasBearing() && mNetworkLocation.hasBearing() &&
2986fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                mNetworkWeight > WEIGHT_THRESHOLD) {
2996fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setBearing(mNetworkLocation.getBearing());   // use Network
3006fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (mGpsLocation.hasBearing() && mNetworkLocation.hasBearing()) {
3016fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            fused.setBearing((float)weigh(mGpsLocation.getBearing(), mNetworkLocation.getBearing(),
3026fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly                    0.0, 360.0));
3036fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
3046fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
30509016ab4dd056a16809419d612cb865a14980880Victoria Lease        if (mNetworkLocation != null) {
30609016ab4dd056a16809419d612cb865a14980880Victoria Lease            fused.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, mNetworkLocation);
30709016ab4dd056a16809419d612cb865a14980880Victoria Lease        }
30809016ab4dd056a16809419d612cb865a14980880Victoria Lease
3096fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mFusedLocation = fused;
3106fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3116fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        mCallback.reportLocation(mFusedLocation);
3126fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
3136fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3146fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /** Called on mLooper thread */
3156fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    @Override
3166fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void onLocationChanged(Location location) {
3176fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (GPS.equals(location.getProvider())) {
3186fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            mGpsLocation = location;
3196fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            updateFusedLocation();
3206fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        } else if (NETWORK.equals(location.getProvider())) {
3216fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            mNetworkLocation = location;
3226fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly            updateFusedLocation();
3236fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        }
3246fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
3256fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3266fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /** Called on mLooper thread */
3276fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    @Override
3286fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void onStatusChanged(String provider, int status, Bundle extras) {  }
3296fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3306fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /** Called on mLooper thread */
3316fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    @Override
3326fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void onProviderEnabled(String provider) {
3336fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        ProviderStats stats = mStats.get(provider);
3346fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (stats == null) return;
3356fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3366fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        stats.available = true;
3376fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
3386fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3396fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    /** Called on mLooper thread */
3406fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    @Override
3416fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void onProviderDisabled(String provider) {
3426fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        ProviderStats stats = mStats.get(provider);
3436fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        if (stats == null) return;
3446fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3456fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        stats.available = false;
3466fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
3476fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly
3486fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3496fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        StringBuilder s = new StringBuilder();
3506fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n');
3516fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        s.append("fused=").append(mFusedLocation).append('\n');
3526fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        s.append(String.format("gps %.3f %s\n", mGpsWeight, mGpsLocation));
3536fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        s.append("    ").append(mStats.get(GPS)).append('\n');
3546fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        s.append(String.format("net %.3f %s\n", mNetworkWeight, mNetworkLocation));
3556fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        s.append("    ").append(mStats.get(NETWORK)).append('\n');
3566fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly        pw.append(s);
3576fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly    }
3586fa9ad4afcd762aea519ff61811386c23d18ddb2Nick Pelly}
359