Use LocationManager.getLastPosition() in GeofenceManager instead of keeping track of it manually. Keeping track of it in GeofenceManager doesn't handle the case where we install a fence, and cross it just after that based on the last position before we installed the fence. Also shuffle around some code in LocationManagerService to remember the last position even if there are no UpdateRecords. This is useful in the GeofenceManager for example. Bug: 7047435 Change-Id: Ia8acc32e357ecc2e1bd689432a5beb1ea7dcd1c7
257 lines
8.8 KiB
Java
257 lines
8.8 KiB
Java
/*
|
|
* Copyright (C) 20012 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.server.location;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
import android.Manifest.permission;
|
|
import android.app.PendingIntent;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.location.Geofence;
|
|
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.PowerManager;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
|
|
import com.android.server.LocationManagerService;
|
|
|
|
public class GeofenceManager implements LocationListener, PendingIntent.OnFinished {
|
|
private static final String TAG = "GeofenceManager";
|
|
private static final boolean D = LocationManagerService.D;
|
|
|
|
/**
|
|
* Assume a maximum land speed, as a heuristic to throttle location updates.
|
|
* (Air travel should result in an airplane mode toggle which will
|
|
* force a new location update anyway).
|
|
*/
|
|
private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train)
|
|
|
|
private final Context mContext;
|
|
private final LocationManager mLocationManager;
|
|
private final PowerManager.WakeLock mWakeLock;
|
|
private final Looper mLooper; // looper thread to take location updates on
|
|
private final LocationBlacklist mBlacklist;
|
|
|
|
private Object mLock = new Object();
|
|
|
|
// access to members below is synchronized on mLock
|
|
private List<GeofenceState> mFences = new LinkedList<GeofenceState>();
|
|
|
|
public GeofenceManager(Context context, LocationBlacklist blacklist) {
|
|
mContext = context;
|
|
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
|
|
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
|
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
|
mLooper = Looper.myLooper();
|
|
mBlacklist = blacklist;
|
|
|
|
LocationRequest request = new LocationRequest()
|
|
.setQuality(LocationRequest.POWER_NONE)
|
|
.setFastestInterval(0);
|
|
mLocationManager.requestLocationUpdates(request, this, Looper.myLooper());
|
|
}
|
|
|
|
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int uid,
|
|
String packageName) {
|
|
Location lastLocation = mLocationManager.getLastLocation();
|
|
GeofenceState state = new GeofenceState(geofence, lastLocation,
|
|
request.getExpireAt(), packageName, intent);
|
|
|
|
synchronized (mLock) {
|
|
// first make sure it doesn't already exist
|
|
for (int i = mFences.size() - 1; i >= 0; i--) {
|
|
GeofenceState w = mFences.get(i);
|
|
if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) {
|
|
// already exists, remove the old one
|
|
mFences.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
mFences.add(state);
|
|
updateProviderRequirementsLocked();
|
|
}
|
|
}
|
|
|
|
public void removeFence(Geofence fence, PendingIntent intent) {
|
|
synchronized (mLock) {
|
|
Iterator<GeofenceState> iter = mFences.iterator();
|
|
while (iter.hasNext()) {
|
|
GeofenceState state = iter.next();
|
|
if (state.mIntent.equals(intent)) {
|
|
|
|
if (fence == null) {
|
|
// alwaus remove
|
|
iter.remove();
|
|
} else {
|
|
// just remove matching fences
|
|
if (fence.equals(state.mFence)) {
|
|
iter.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
updateProviderRequirementsLocked();
|
|
}
|
|
}
|
|
|
|
public void removeFence(String packageName) {
|
|
synchronized (mLock) {
|
|
Iterator<GeofenceState> iter = mFences.iterator();
|
|
while (iter.hasNext()) {
|
|
GeofenceState state = iter.next();
|
|
if (state.mPackageName.equals(packageName)) {
|
|
iter.remove();
|
|
}
|
|
}
|
|
updateProviderRequirementsLocked();
|
|
}
|
|
}
|
|
|
|
private void removeExpiredFencesLocked() {
|
|
long time = SystemClock.elapsedRealtime();
|
|
Iterator<GeofenceState> iter = mFences.iterator();
|
|
while (iter.hasNext()) {
|
|
GeofenceState state = iter.next();
|
|
if (state.mExpireAt < time) {
|
|
iter.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void processLocation(Location location) {
|
|
List<PendingIntent> enterIntents = new LinkedList<PendingIntent>();
|
|
List<PendingIntent> exitIntents = new LinkedList<PendingIntent>();
|
|
|
|
synchronized (mLock) {
|
|
removeExpiredFencesLocked();
|
|
|
|
for (GeofenceState state : mFences) {
|
|
if (mBlacklist.isBlacklisted(state.mPackageName)) {
|
|
if (D) Log.d(TAG, "skipping geofence processing for blacklisted app: " +
|
|
state.mPackageName);
|
|
continue;
|
|
}
|
|
|
|
int event = state.processLocation(location);
|
|
if ((event & GeofenceState.FLAG_ENTER) != 0) {
|
|
enterIntents.add(state.mIntent);
|
|
}
|
|
if ((event & GeofenceState.FLAG_EXIT) != 0) {
|
|
exitIntents.add(state.mIntent);
|
|
}
|
|
}
|
|
updateProviderRequirementsLocked();
|
|
}
|
|
|
|
// release lock before sending intents
|
|
for (PendingIntent intent : exitIntents) {
|
|
sendIntentExit(intent);
|
|
}
|
|
for (PendingIntent intent : enterIntents) {
|
|
sendIntentEnter(intent);
|
|
}
|
|
}
|
|
|
|
private void sendIntentEnter(PendingIntent pendingIntent) {
|
|
Intent intent = new Intent();
|
|
intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
|
|
sendIntent(pendingIntent, intent);
|
|
}
|
|
|
|
private void sendIntentExit(PendingIntent pendingIntent) {
|
|
Intent intent = new Intent();
|
|
intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
|
|
sendIntent(pendingIntent, intent);
|
|
}
|
|
|
|
private void sendIntent(PendingIntent pendingIntent, Intent intent) {
|
|
try {
|
|
mWakeLock.acquire();
|
|
pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION);
|
|
} catch (PendingIntent.CanceledException e) {
|
|
removeFence(null, pendingIntent);
|
|
mWakeLock.release();
|
|
}
|
|
}
|
|
|
|
private void updateProviderRequirementsLocked() {
|
|
double minDistance = Double.MAX_VALUE;
|
|
for (GeofenceState state : mFences) {
|
|
if (state.getDistance() < minDistance) {
|
|
minDistance = state.getDistance();
|
|
}
|
|
}
|
|
|
|
if (minDistance == Double.MAX_VALUE) {
|
|
disableLocationLocked();
|
|
} else {
|
|
int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S;
|
|
requestLocationLocked(intervalMs);
|
|
}
|
|
}
|
|
|
|
private void requestLocationLocked(int intervalMs) {
|
|
mLocationManager.requestLocationUpdates(new LocationRequest().setInterval(intervalMs), this,
|
|
mLooper);
|
|
}
|
|
|
|
private void disableLocationLocked() {
|
|
mLocationManager.removeUpdates(this);
|
|
}
|
|
|
|
@Override
|
|
public void onLocationChanged(Location location) {
|
|
processLocation(location);
|
|
}
|
|
|
|
@Override
|
|
public void onStatusChanged(String provider, int status, Bundle extras) { }
|
|
|
|
@Override
|
|
public void onProviderEnabled(String provider) { }
|
|
|
|
@Override
|
|
public void onProviderDisabled(String provider) { }
|
|
|
|
@Override
|
|
public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
|
|
String resultData, Bundle resultExtras) {
|
|
mWakeLock.release();
|
|
}
|
|
|
|
public void dump(PrintWriter pw) {
|
|
pw.println(" Geofences:");
|
|
|
|
for (GeofenceState state : mFences) {
|
|
pw.append(" ");
|
|
pw.append(state.mPackageName);
|
|
pw.append(" ");
|
|
pw.append(state.mFence.toString());
|
|
pw.append("\n");
|
|
}
|
|
}
|
|
}
|