blob: fb7dbc8aca5c6a3f276dd3291124e3c87e896b4a [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.location.fused;
import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.NETWORK_PROVIDER;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.os.Bundle;
import android.os.Looper;
import android.os.Parcelable;
import android.os.WorkSource;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.location.ProviderRequest;
import com.android.location.provider.LocationProviderBase;
import com.android.location.provider.LocationRequestUnbundled;
import com.android.location.provider.ProviderPropertiesUnbundled;
import com.android.location.provider.ProviderRequestUnbundled;
import java.io.PrintWriter;
/** Basic fused location provider implementation. */
public class FusedLocationProvider extends LocationProviderBase {
private static final String TAG = "FusedLocationProvider";
private static final ProviderPropertiesUnbundled PROPERTIES =
ProviderPropertiesUnbundled.create(
/* requiresNetwork = */ false,
/* requiresSatellite = */ false,
/* requiresCell = */ false,
/* hasMonetaryCost = */ false,
/* supportsAltitude = */ true,
/* supportsSpeed = */ true,
/* supportsBearing = */ true,
Criteria.POWER_LOW,
Criteria.ACCURACY_FINE
);
private static final long MAX_LOCATION_COMPARISON_NS = 11 * 1000000000L; // 11 seconds
private final Object mLock = new Object();
private final Context mContext;
private final LocationManager mLocationManager;
private final LocationListener mGpsListener;
private final LocationListener mNetworkListener;
private final BroadcastReceiver mUserChangeReceiver;
@GuardedBy("mLock")
private ProviderRequestUnbundled mRequest;
@GuardedBy("mLock")
private WorkSource mWorkSource;
@GuardedBy("mLock")
private long mGpsInterval;
@GuardedBy("mLock")
private long mNetworkInterval;
@GuardedBy("mLock")
@Nullable private Location mFusedLocation;
@GuardedBy("mLock")
@Nullable private Location mGpsLocation;
@GuardedBy("mLock")
@Nullable private Location mNetworkLocation;
public FusedLocationProvider(Context context) {
super(TAG, PROPERTIES);
mContext = context;
mLocationManager = context.getSystemService(LocationManager.class);
mGpsListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
synchronized (mLock) {
mGpsLocation = location;
reportBestLocationLocked();
}
}
@Override
public void onProviderDisabled(String provider) {
synchronized (mLock) {
// if satisfying a bypass request, don't clear anything
if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
return;
}
mGpsLocation = null;
}
}
};
mNetworkListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
synchronized (mLock) {
mNetworkLocation = location;
reportBestLocationLocked();
}
}
@Override
public void onProviderDisabled(String provider) {
synchronized (mLock) {
// if satisfying a bypass request, don't clear anything
if (mRequest.getReportLocation() && mRequest.isLocationSettingsIgnored()) {
return;
}
mNetworkLocation = null;
}
}
};
mUserChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!ACTION_USER_SWITCHED.equals(intent.getAction())) {
return;
}
onUserChanged();
}
};
mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
mWorkSource = new WorkSource();
mGpsInterval = Long.MAX_VALUE;
mNetworkInterval = Long.MAX_VALUE;
}
void start() {
mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED));
}
void stop() {
mContext.unregisterReceiver(mUserChangeReceiver);
synchronized (mLock) {
mRequest = new ProviderRequestUnbundled(ProviderRequest.EMPTY_REQUEST);
updateRequirementsLocked();
}
}
@Override
public void onSetRequest(ProviderRequestUnbundled request, WorkSource workSource) {
synchronized (mLock) {
mRequest = request;
mWorkSource = workSource;
updateRequirementsLocked();
}
}
@GuardedBy("mLock")
private void updateRequirementsLocked() {
long gpsInterval = Long.MAX_VALUE;
long networkInterval = Long.MAX_VALUE;
if (mRequest.getReportLocation()) {
for (LocationRequestUnbundled request : mRequest.getLocationRequests()) {
switch (request.getQuality()) {
case LocationRequestUnbundled.ACCURACY_FINE:
case LocationRequestUnbundled.POWER_HIGH:
if (request.getInterval() < gpsInterval) {
gpsInterval = request.getInterval();
}
if (request.getInterval() < networkInterval) {
networkInterval = request.getInterval();
}
break;
case LocationRequestUnbundled.ACCURACY_BLOCK:
case LocationRequestUnbundled.ACCURACY_CITY:
case LocationRequestUnbundled.POWER_LOW:
if (request.getInterval() < networkInterval) {
networkInterval = request.getInterval();
}
break;
}
}
}
if (gpsInterval != mGpsInterval) {
resetProviderRequestLocked(GPS_PROVIDER, mGpsInterval, gpsInterval, mGpsListener);
mGpsInterval = gpsInterval;
}
if (networkInterval != mNetworkInterval) {
resetProviderRequestLocked(NETWORK_PROVIDER, mNetworkInterval, networkInterval,
mNetworkListener);
mNetworkInterval = networkInterval;
}
}
@GuardedBy("mLock")
private void resetProviderRequestLocked(String provider, long oldInterval, long newInterval,
LocationListener listener) {
if (oldInterval != Long.MAX_VALUE) {
mLocationManager.removeUpdates(listener);
}
if (newInterval != Long.MAX_VALUE) {
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, newInterval, 0, false);
if (mRequest.isLocationSettingsIgnored()) {
request.setLocationSettingsIgnored(true);
}
request.setWorkSource(mWorkSource);
mLocationManager.requestLocationUpdates(request, listener, Looper.getMainLooper());
}
}
@GuardedBy("mLock")
private void reportBestLocationLocked() {
Location bestLocation = chooseBestLocation(mGpsLocation, mNetworkLocation);
if (bestLocation == mFusedLocation) {
return;
}
mFusedLocation = bestLocation;
if (mFusedLocation == null) {
return;
}
// copy NO_GPS_LOCATION extra from mNetworkLocation into mFusedLocation
if (mNetworkLocation != null) {
Bundle srcExtras = mNetworkLocation.getExtras();
if (srcExtras != null) {
Parcelable srcParcelable =
srcExtras.getParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION);
if (srcParcelable instanceof Location) {
Bundle dstExtras = mFusedLocation.getExtras();
if (dstExtras == null) {
dstExtras = new Bundle();
mFusedLocation.setExtras(dstExtras);
}
dstExtras.putParcelable(LocationProviderBase.EXTRA_NO_GPS_LOCATION,
srcParcelable);
}
}
}
reportLocation(mFusedLocation);
}
private void onUserChanged() {
// clear cached locations when the user changes to prevent leaking user information
synchronized (mLock) {
mFusedLocation = null;
mGpsLocation = null;
mNetworkLocation = null;
}
}
void dump(PrintWriter writer) {
synchronized (mLock) {
writer.println("request: " + mRequest);
if (mGpsInterval != Long.MAX_VALUE) {
writer.println(" gps interval: " + mGpsInterval);
}
if (mNetworkInterval != Long.MAX_VALUE) {
writer.println(" network interval: " + mNetworkInterval);
}
if (mGpsLocation != null) {
writer.println(" last gps location: " + mGpsLocation);
}
if (mNetworkLocation != null) {
writer.println(" last network location: " + mNetworkLocation);
}
}
}
@Nullable
private static Location chooseBestLocation(
@Nullable Location locationA,
@Nullable Location locationB) {
if (locationA == null) {
return locationB;
}
if (locationB == null) {
return locationA;
}
if (locationA.getElapsedRealtimeNanos()
> locationB.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
return locationA;
}
if (locationB.getElapsedRealtimeNanos()
> locationA.getElapsedRealtimeNanos() + MAX_LOCATION_COMPARISON_NS) {
return locationB;
}
if (!locationA.hasAccuracy()) {
return locationB;
}
if (!locationB.hasAccuracy()) {
return locationA;
}
return locationA.getAccuracy() < locationB.getAccuracy() ? locationA : locationB;
}
}