Loading services/java/com/android/server/location/GeofenceManager.java +225 −56 Original line number Diff line number Diff line /* * 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. Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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) { if (D) { Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName); } public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int uid, String packageName) { Location lastLocation = mLocationManager.getLastLocation(); GeofenceState state = new GeofenceState(geofence, lastLocation, 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--) { Loading @@ -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()) { Loading @@ -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 Loading @@ -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()) { Loading @@ -126,7 +176,7 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish iter.remove(); } } updateProviderRequirementsLocked(); scheduleUpdateFencesLocked(); } } Loading @@ -141,20 +191,78 @@ 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; } needUpdates = true; if (location != null) { int event = state.processLocation(location); if ((event & GeofenceState.FLAG_ENTER) != 0) { enterIntents.add(state.mIntent); Loading @@ -162,8 +270,54 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish 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 (!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 Loading @@ -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) { try { mWakeLock.acquire(); pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); try { 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(); // Update the fences immediately before returning in // case the caller is holding a wakelock. if (mPendingUpdate) { mHandler.removeMessages(MSG_UPDATE_FENCES); } else { int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; requestLocationLocked(intervalMs); 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 Loading Loading @@ -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; } } } } } services/java/com/android/server/location/GeofenceState.java +22 −16 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -53,10 +54,6 @@ public class GeofenceState { mLocation = new Location(""); mLocation.setLatitude(fence.getLatitude()); mLocation.setLongitude(fence.getLongitude()); if (prevLocation != null) { processLocation(prevLocation); } } /** Loading @@ -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 == STATE_INSIDE) { return FLAG_EXIT; // return exit only if previously entered } if (prevState != 0 && mState != prevState) { if (mState == STATE_INSIDE) return FLAG_ENTER; if (mState == STATE_OUTSIDE) return FLAG_EXIT; } 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 Loading @@ -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); } } Loading
services/java/com/android/server/location/GeofenceManager.java +225 −56 Original line number Diff line number Diff line /* * 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. Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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) { if (D) { Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName); } public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int uid, String packageName) { Location lastLocation = mLocationManager.getLastLocation(); GeofenceState state = new GeofenceState(geofence, lastLocation, 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--) { Loading @@ -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()) { Loading @@ -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 Loading @@ -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()) { Loading @@ -126,7 +176,7 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish iter.remove(); } } updateProviderRequirementsLocked(); scheduleUpdateFencesLocked(); } } Loading @@ -141,20 +191,78 @@ 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; } needUpdates = true; if (location != null) { int event = state.processLocation(location); if ((event & GeofenceState.FLAG_ENTER) != 0) { enterIntents.add(state.mIntent); Loading @@ -162,8 +270,54 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish 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 (!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 Loading @@ -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) { try { mWakeLock.acquire(); pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); try { 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(); // Update the fences immediately before returning in // case the caller is holding a wakelock. if (mPendingUpdate) { mHandler.removeMessages(MSG_UPDATE_FENCES); } else { int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; requestLocationLocked(intervalMs); 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 Loading Loading @@ -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; } } } } }
services/java/com/android/server/location/GeofenceState.java +22 −16 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -53,10 +54,6 @@ public class GeofenceState { mLocation = new Location(""); mLocation.setLatitude(fence.getLatitude()); mLocation.setLongitude(fence.getLongitude()); if (prevLocation != null) { processLocation(prevLocation); } } /** Loading @@ -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 == STATE_INSIDE) { return FLAG_EXIT; // return exit only if previously entered } if (prevState != 0 && mState != prevState) { if (mState == STATE_INSIDE) return FLAG_ENTER; if (mState == STATE_OUTSIDE) return FLAG_EXIT; } 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 Loading @@ -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); } }