diff options
author | Victoria Lease <violets@google.com> | 2012-11-08 06:13:21 -0800 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2012-11-08 06:13:21 -0800 |
commit | 44d927ca837f03e99a4dda52377275da2863ecbb (patch) | |
tree | a685df69c91b681eb29f20296e4d19c56de63f4d /services | |
parent | 744c520472d59c59f5320e1b0cf0801418eba773 (diff) | |
parent | 66f3d7f3c8247b59516cbc4ad13bea4e0b8c0ef9 (diff) | |
download | frameworks_base-44d927ca837f03e99a4dda52377275da2863ecbb.zip frameworks_base-44d927ca837f03e99a4dda52377275da2863ecbb.tar.gz frameworks_base-44d927ca837f03e99a4dda52377275da2863ecbb.tar.bz2 |
am 66f3d7f3: am 446ffe00: am e6299d5a: Merge "Fires geofence if the device is already in the geofence area." into jb-mr1-dev
* commit '66f3d7f3c8247b59516cbc4ad13bea4e0b8c0ef9':
Fires geofence if the device is already in the geofence area.
Diffstat (limited to 'services')
-rw-r--r-- | services/java/com/android/server/location/GeofenceManager.java | 281 | ||||
-rw-r--r-- | services/java/com/android/server/location/GeofenceState.java | 38 |
2 files changed, 247 insertions, 72 deletions
diff --git a/services/java/com/android/server/location/GeofenceManager.java b/services/java/com/android/server/location/GeofenceManager.java index d04d2f3..f9be719 100644 --- a/services/java/com/android/server/location/GeofenceManager.java +++ b/services/java/com/android/server/location/GeofenceManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 20012 The Android Open Source Project + * 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. @@ -21,7 +21,6 @@ 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; @@ -31,10 +30,11 @@ import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationRequest; import android.os.Bundle; -import android.os.Looper; +import android.os.Handler; +import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; -import android.util.Log; +import android.util.Slog; import com.android.server.LocationManagerService; @@ -42,6 +42,8 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish private static final String TAG = "GeofenceManager"; private static final boolean D = LocationManagerService.D; + private static final int MSG_UPDATE_FENCES = 1; + /** * Assume a maximum land speed, as a heuristic to throttle location updates. * (Air travel should result in an airplane mode toggle which will @@ -49,37 +51,77 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish */ private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) + /** + * Maximum age after which a location is no longer considered fresh enough to use. + */ + private static final long MAX_AGE_NANOS = 5 * 60 * 1000000000L; // five minutes + + /** + * Most frequent update interval allowed. + */ + private static final long MIN_INTERVAL_MS = 1 * 60 * 1000; // one minute + + /** + * Least frequent update interval allowed. + */ + private static final long MAX_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours + 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 GeofenceHandler mHandler; private final LocationBlacklist mBlacklist; private Object mLock = new Object(); // access to members below is synchronized on mLock + /** + * A list containing all registered geofences. + */ private List<GeofenceState> mFences = new LinkedList<GeofenceState>(); + /** + * This is set true when we have an active request for {@link Location} updates via + * {@link LocationManager#requestLocationUpdates(LocationRequest, LocationListener, + * android.os.Looper). + */ + private boolean mReceivingLocationUpdates; + + /** + * The update interval component of the current active {@link Location} update request. + */ + private long mLocationUpdateInterval; + + /** + * The {@link Location} most recently received via {@link #onLocationChanged(Location)}. + */ + private Location mLastLocationUpdate; + + /** + * This is set true when a {@link Location} is received via + * {@link #onLocationChanged(Location)} or {@link #scheduleUpdateFencesLocked()}, and cleared + * when that Location has been processed via {@link #updateFences()} + */ + private boolean mPendingUpdate; + 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(); + mHandler = new GeofenceHandler(); 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); + public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, + int uid, String packageName) { + if (D) { + Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence + + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName); + } + GeofenceState state = new GeofenceState(geofence, + request.getExpireAt(), packageName, intent); synchronized (mLock) { // first make sure it doesn't already exist for (int i = mFences.size() - 1; i >= 0; i--) { @@ -91,11 +133,15 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish } } mFences.add(state); - updateProviderRequirementsLocked(); + scheduleUpdateFencesLocked(); } } public void removeFence(Geofence fence, PendingIntent intent) { + if (D) { + Slog.d(TAG, "removeFence: fence=" + fence + ", intent=" + intent); + } + synchronized (mLock) { Iterator<GeofenceState> iter = mFences.iterator(); while (iter.hasNext()) { @@ -103,7 +149,7 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish if (state.mIntent.equals(intent)) { if (fence == null) { - // alwaus remove + // always remove iter.remove(); } else { // just remove matching fences @@ -113,11 +159,15 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish } } } - updateProviderRequirementsLocked(); + scheduleUpdateFencesLocked(); } } public void removeFence(String packageName) { + if (D) { + Slog.d(TAG, "removeFence: packageName=" + packageName); + } + synchronized (mLock) { Iterator<GeofenceState> iter = mFences.iterator(); while (iter.hasNext()) { @@ -126,7 +176,7 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish iter.remove(); } } - updateProviderRequirementsLocked(); + scheduleUpdateFencesLocked(); } } @@ -141,29 +191,133 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish } } - private void processLocation(Location location) { + private void scheduleUpdateFencesLocked() { + if (!mPendingUpdate) { + mPendingUpdate = true; + mHandler.sendEmptyMessage(MSG_UPDATE_FENCES); + } + } + + /** + * Returns the location received most recently from {@link #onLocationChanged(Location)}, + * or consult {@link LocationManager#getLastLocation()} if none has arrived. Does not return + * either if the location would be too stale to be useful. + * + * @return a fresh, valid Location, or null if none is available + */ + private Location getFreshLocationLocked() { + // Prefer mLastLocationUpdate to LocationManager.getLastLocation(). + Location location = mReceivingLocationUpdates ? mLastLocationUpdate : null; + if (location == null && !mFences.isEmpty()) { + location = mLocationManager.getLastLocation(); + } + + // Early out for null location. + if (location == null) { + return null; + } + + // Early out for stale location. + long now = SystemClock.elapsedRealtimeNanos(); + if (now - location.getElapsedRealtimeNanos() > MAX_AGE_NANOS) { + return null; + } + + // Made it this far? Return our fresh, valid location. + return location; + } + + /** + * The geofence update loop. This function removes expired fences, then tests the most + * recently-received {@link Location} against each registered {@link GeofenceState}, sending + * {@link Intent}s for geofences that have been tripped. It also adjusts the active location + * update request with {@link LocationManager} as appropriate for any active geofences. + */ + // Runs on the handler. + private void updateFences() { List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); synchronized (mLock) { + mPendingUpdate = false; + + // Remove expired fences. removeExpiredFencesLocked(); + // Get a location to work with, either received via onLocationChanged() or + // via LocationManager.getLastLocation(). + Location location = getFreshLocationLocked(); + + // Update all fences. + // Keep track of the distance to the nearest fence. + double minFenceDistance = Double.MAX_VALUE; + boolean needUpdates = false; for (GeofenceState state : mFences) { if (mBlacklist.isBlacklisted(state.mPackageName)) { - if (D) Log.d(TAG, "skipping geofence processing for blacklisted app: " + - state.mPackageName); + if (D) { + Slog.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); + needUpdates = true; + if (location != null) { + 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); + } + + // FIXME: Ideally this code should take into account the accuracy of the + // location fix that was used to calculate the distance in the first place. + double fenceDistance = state.getDistanceToBoundary(); // MAX_VALUE if unknown + if (fenceDistance < minFenceDistance) { + minFenceDistance = fenceDistance; + } + } + } + + // Request or cancel location updates if needed. + if (needUpdates) { + // Request location updates. + // Compute a location update interval based on the distance to the nearest fence. + long intervalMs; + if (location != null && Double.compare(minFenceDistance, Double.MAX_VALUE) != 0) { + intervalMs = (long)Math.min(MAX_INTERVAL_MS, Math.max(MIN_INTERVAL_MS, + minFenceDistance * 1000 / MAX_SPEED_M_S)); + } else { + intervalMs = MIN_INTERVAL_MS; } - if ((event & GeofenceState.FLAG_EXIT) != 0) { - exitIntents.add(state.mIntent); + if (!mReceivingLocationUpdates || mLocationUpdateInterval != intervalMs) { + mReceivingLocationUpdates = true; + mLocationUpdateInterval = intervalMs; + mLastLocationUpdate = location; + + LocationRequest request = new LocationRequest(); + request.setInterval(intervalMs).setFastestInterval(0); + mLocationManager.requestLocationUpdates(request, this, mHandler.getLooper()); } + } else { + // Cancel location updates. + if (mReceivingLocationUpdates) { + mReceivingLocationUpdates = false; + mLocationUpdateInterval = 0; + mLastLocationUpdate = null; + + mLocationManager.removeUpdates(this); + } + } + + if (D) { + Slog.d(TAG, "updateFences: location=" + location + + ", mFences.size()=" + mFences.size() + + ", mReceivingLocationUpdates=" + mReceivingLocationUpdates + + ", mLocationUpdateInterval=" + mLocationUpdateInterval + + ", mLastLocationUpdate=" + mLastLocationUpdate); } - updateProviderRequirementsLocked(); } // release lock before sending intents @@ -176,55 +330,54 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish } private void sendIntentEnter(PendingIntent pendingIntent) { + if (D) { + Slog.d(TAG, "sendIntentEnter: pendingIntent=" + pendingIntent); + } + Intent intent = new Intent(); intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); sendIntent(pendingIntent, intent); } private void sendIntentExit(PendingIntent pendingIntent) { + if (D) { + Slog.d(TAG, "sendIntentExit: pendingIntent=" + pendingIntent); + } + Intent intent = new Intent(); intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); sendIntent(pendingIntent, intent); } private void sendIntent(PendingIntent pendingIntent, Intent intent) { + mWakeLock.acquire(); try { - mWakeLock.acquire(); - pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); + pendingIntent.send(mContext, 0, intent, this, null, + android.Manifest.permission.ACCESS_FINE_LOCATION); } catch (PendingIntent.CanceledException e) { removeFence(null, pendingIntent); mWakeLock.release(); } + // ...otherwise, mWakeLock.release() gets called by onSendFinished() } - private void updateProviderRequirementsLocked() { - double minDistance = Double.MAX_VALUE; - for (GeofenceState state : mFences) { - if (state.getDistance() < minDistance) { - minDistance = state.getDistance(); + // Runs on the handler (which was passed into LocationManager.requestLocationUpdates()) + @Override + public void onLocationChanged(Location location) { + synchronized (mLock) { + if (mReceivingLocationUpdates) { + mLastLocationUpdate = location; } - } - if (minDistance == Double.MAX_VALUE) { - disableLocationLocked(); - } else { - int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; - requestLocationLocked(intervalMs); + // Update the fences immediately before returning in + // case the caller is holding a wakelock. + if (mPendingUpdate) { + mHandler.removeMessages(MSG_UPDATE_FENCES); + } else { + mPendingUpdate = true; + } } - } - - 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); + updateFences(); } @Override @@ -253,4 +406,20 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish pw.append("\n"); } } + + private final class GeofenceHandler extends Handler { + public GeofenceHandler() { + super(true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_FENCES: { + updateFences(); + break; + } + } + } + } } diff --git a/services/java/com/android/server/location/GeofenceState.java b/services/java/com/android/server/location/GeofenceState.java index 1fd737f..11705ff 100644 --- a/services/java/com/android/server/location/GeofenceState.java +++ b/services/java/com/android/server/location/GeofenceState.java @@ -39,11 +39,12 @@ public class GeofenceState { public final PendingIntent mIntent; int mState; // current state - double mDistance; // current distance to center of fence + double mDistanceToCenter; // current distance to center of fence - public GeofenceState(Geofence fence, Location prevLocation, long expireAt, + public GeofenceState(Geofence fence, long expireAt, String packageName, PendingIntent intent) { mState = STATE_UNKNOWN; + mDistanceToCenter = Double.MAX_VALUE; mFence = fence; mExpireAt = expireAt; @@ -53,10 +54,6 @@ public class GeofenceState { mLocation = new Location(""); mLocation.setLatitude(fence.getLatitude()); mLocation.setLongitude(fence.getLongitude()); - - if (prevLocation != null) { - processLocation(prevLocation); - } } /** @@ -64,26 +61,35 @@ public class GeofenceState { * @return FLAG_ENTER or FLAG_EXIT if the fence was crossed, 0 otherwise */ public int processLocation(Location location) { - mDistance = mLocation.distanceTo(location); + mDistanceToCenter = mLocation.distanceTo(location); int prevState = mState; //TODO: inside/outside detection could be made more rigorous - boolean inside = mDistance <= Math.max(mFence.getRadius(), location.getAccuracy()); + boolean inside = mDistanceToCenter <= Math.max(mFence.getRadius(), location.getAccuracy()); if (inside) { mState = STATE_INSIDE; + if (prevState != STATE_INSIDE) { + return FLAG_ENTER; // return enter if previously exited or unknown + } } else { mState = STATE_OUTSIDE; - } - - if (prevState != 0 && mState != prevState) { - if (mState == STATE_INSIDE) return FLAG_ENTER; - if (mState == STATE_OUTSIDE) return FLAG_EXIT; + if (prevState == STATE_INSIDE) { + return FLAG_EXIT; // return exit only if previously entered + } } return 0; } - public double getDistance() { - return mDistance; + /** + * Gets the distance from the current location to the fence's boundary. + * @return The distance or {@link Double#MAX_VALUE} if unknown. + */ + public double getDistanceToBoundary() { + if (Double.compare(mDistanceToCenter, Double.MAX_VALUE) == 0) { + return Double.MAX_VALUE; + } else { + return Math.abs(mFence.getRadius() - mDistanceToCenter); + } } @Override @@ -99,6 +105,6 @@ public class GeofenceState { default: state = "?"; } - return String.format("%s d=%.0f %s", mFence.toString(), mDistance, state); + return String.format("%s d=%.0f %s", mFence.toString(), mDistanceToCenter, state); } } |