blob: 7a239af98755496461a9eaf49308732f94274e45 [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 static android.location.LocationRequest.QUALITY_LOW_POWER;
import static android.location.provider.ProviderProperties.ACCURACY_FINE;
import static android.location.provider.ProviderProperties.POWER_USAGE_LOW;
import static com.android.location.provider.ProviderRequestUnbundled.INTERVAL_DISABLED;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.provider.LocationProviderBase;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.os.Bundle;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.Objects;
/** Basic fused location provider implementation. */
public class FusedLocationProvider extends LocationProviderBase {
private static final String TAG = "FusedLocationProvider";
private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder()
.setHasAltitudeSupport(true)
.setHasSpeedSupport(true)
.setHasBearingSupport(true)
.setPowerUsage(POWER_USAGE_LOW)
.setAccuracy(ACCURACY_FINE)
.build();
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 ChildLocationListener mGpsListener;
private final ChildLocationListener mNetworkListener;
private final BroadcastReceiver mUserChangeReceiver;
@GuardedBy("mLock")
private ProviderRequest mRequest;
@GuardedBy("mLock")
private @Nullable Location mFusedLocation;
public FusedLocationProvider(Context context) {
super(context, TAG, PROPERTIES);
mContext = context;
mLocationManager = Objects.requireNonNull(context.getSystemService(LocationManager.class));
mGpsListener = new ChildLocationListener(GPS_PROVIDER);
mNetworkListener = new ChildLocationListener(NETWORK_PROVIDER);
mUserChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!ACTION_USER_SWITCHED.equals(intent.getAction())) {
return;
}
onUserChanged();
}
};
mRequest = ProviderRequest.EMPTY_REQUEST;
}
void start() {
mContext.registerReceiver(mUserChangeReceiver, new IntentFilter(ACTION_USER_SWITCHED));
}
void stop() {
mContext.unregisterReceiver(mUserChangeReceiver);
synchronized (mLock) {
mRequest = ProviderRequest.EMPTY_REQUEST;
updateRequirementsLocked();
}
}
@Override
public void onSetRequest(ProviderRequest request) {
synchronized (mLock) {
mRequest = request;
updateRequirementsLocked();
}
}
@Override
public void onFlush(OnFlushCompleteCallback callback) {
OnFlushCompleteCallback wrapper = new OnFlushCompleteCallback() {
private int mFlushCount = 2;
@Override
public void onFlushComplete() {
if (--mFlushCount == 0) {
callback.onFlushComplete();
}
}
};
mGpsListener.flush(wrapper);
mNetworkListener.flush(wrapper);
}
@Override
public void onSendExtraCommand(String command, @Nullable Bundle extras) {}
@GuardedBy("mLock")
private void updateRequirementsLocked() {
long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getIntervalMillis()
: INTERVAL_DISABLED;
long networkInterval = mRequest.getIntervalMillis();
mGpsListener.resetProviderRequest(gpsInterval);
mNetworkListener.resetProviderRequest(networkInterval);
}
@GuardedBy("mLock")
void reportBestLocationLocked() {
Location bestLocation = chooseBestLocation(mGpsListener.getLocation(),
mNetworkListener.getLocation());
if (bestLocation == mFusedLocation) {
return;
}
mFusedLocation = bestLocation;
if (mFusedLocation == null) {
return;
}
reportLocation(mFusedLocation);
}
void onUserChanged() {
// clear cached locations when the user changes to prevent leaking user information
synchronized (mLock) {
mFusedLocation = null;
mGpsListener.clearLocation();
mNetworkListener.clearLocation();
}
}
void dump(PrintWriter writer) {
synchronized (mLock) {
writer.println("request: " + mRequest);
if (mGpsListener.getInterval() != INTERVAL_DISABLED) {
writer.println(" gps interval: " + mGpsListener.getInterval());
}
if (mNetworkListener.getInterval() != INTERVAL_DISABLED) {
writer.println(" network interval: " + mNetworkListener.getInterval());
}
if (mGpsListener.getLocation() != null) {
writer.println(" last gps location: " + mGpsListener.getLocation());
}
if (mNetworkListener.getLocation() != null) {
writer.println(" last network location: " + mNetworkListener.getLocation());
}
}
}
@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;
}
private class ChildLocationListener implements LocationListener {
private final String mProvider;
private final SparseArray<OnFlushCompleteCallback> mPendingFlushes;
@GuardedBy("mLock")
private int mNextFlushCode = 0;
@GuardedBy("mLock")
private @Nullable Location mLocation = null;
@GuardedBy("mLock")
private long mInterval = INTERVAL_DISABLED;
ChildLocationListener(String provider) {
mProvider = provider;
mPendingFlushes = new SparseArray<>();
}
@Nullable Location getLocation() {
synchronized (mLock) {
return mLocation;
}
}
long getInterval() {
synchronized (mLock) {
return mInterval;
}
}
void clearLocation() {
synchronized (mLock) {
mLocation = null;
}
}
private void resetProviderRequest(long newInterval) {
synchronized (mLock) {
if (newInterval == mInterval) {
return;
}
if (mInterval != INTERVAL_DISABLED && newInterval == INTERVAL_DISABLED) {
mLocationManager.removeUpdates(this);
}
mInterval = newInterval;
if (mInterval != INTERVAL_DISABLED) {
LocationRequest request = new LocationRequest.Builder(mInterval)
.setMaxUpdateDelayMillis(mRequest.getMaxUpdateDelayMillis())
.setQuality(mRequest.getQuality())
.setLowPower(mRequest.isLowPower())
.setLocationSettingsIgnored(mRequest.isLocationSettingsIgnored())
.setWorkSource(mRequest.getWorkSource())
.setHiddenFromAppOps(true)
.build();
mLocationManager.requestLocationUpdates(mProvider, request,
mContext.getMainExecutor(), this);
}
}
}
void flush(OnFlushCompleteCallback callback) {
synchronized (mLock) {
int requestCode = mNextFlushCode++;
mPendingFlushes.put(requestCode, callback);
mLocationManager.requestFlush(mProvider, this, requestCode);
}
}
@Override
public void onLocationChanged(Location location) {
synchronized (mLock) {
mLocation = location;
reportBestLocationLocked();
}
}
@Override
public void onProviderDisabled(String provider) {
synchronized (mLock) {
// if satisfying a bypass request, don't clear anything
if (mRequest.isActive() && mRequest.isLocationSettingsIgnored()) {
return;
}
mLocation = null;
}
}
@Override
public void onFlushComplete(int requestCode) {
synchronized (mLock) {
OnFlushCompleteCallback callback = mPendingFlushes.removeReturnOld(requestCode);
if (callback != null) {
callback.onFlushComplete();
}
}
}
}
}