Files
frameworks_base/services/java/com/android/server/location/GeofenceManager.java
Nick Pelly 4035f5a7c1 Port location blacklist code to MR1.
I had to re-do this change for MR1 because LocationManagerService changed
so much. Here is the original change description:

Add package-name-prefix blacklist for location updates.

The Settings.Secure value locationPackagePrefixBlacklist and
locationPackagePrefixWhitelist contains comma seperated package-name
prefixes.

Location & geo-fence updates are silently dropped if the receiving
package name has a prefix on the blacklist. Status updates are
not affected. All other API's work as before.

A content observer is used so run-time updates to the blacklist
apply immediately. There is both a blacklist and a whitelist.
The blacklist applies first, and then exemptions are allowed
from the whitelist. In other words, if your package name prefix
matches both the black AND white list, then it is allowed.

Bug: 6986553
Change-Id: I1e151e08bd7143e47db005bc3fe9795076398df7
2012-08-17 15:25:16 -07:00

259 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 Location mLastLocation;
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) {
GeofenceState state = new GeofenceState(geofence, mLastLocation,
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) {
mLastLocation = location;
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");
}
}
}