FileDocCategorySizeDatePackage
GeofenceManager.javaAPI DocAndroid 5.1 API16333Thu Mar 12 22:22:42 GMT 2015com.android.server.location

GeofenceManager

public class GeofenceManager extends Object implements android.location.LocationListener, PendingIntent.OnFinished

Fields Summary
private static final String
TAG
private static final boolean
D
private static final int
MSG_UPDATE_FENCES
private static final int
MAX_SPEED_M_S
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 long
MAX_AGE_NANOS
Maximum age after which a location is no longer considered fresh enough to use.
private static final long
MIN_INTERVAL_MS
Most frequent update interval allowed.
private static final long
MAX_INTERVAL_MS
Least frequent update interval allowed.
private final android.content.Context
mContext
private final android.location.LocationManager
mLocationManager
private final android.app.AppOpsManager
mAppOps
private final PowerManager.WakeLock
mWakeLock
private final GeofenceHandler
mHandler
private final LocationBlacklist
mBlacklist
private Object
mLock
private List
mFences
A list containing all registered geofences.
private boolean
mReceivingLocationUpdates
This is set true when we have an active request for {@link Location} updates via {@link LocationManager#requestLocationUpdates(LocationRequest, LocationListener, android.os.Looper).
private long
mLocationUpdateInterval
The update interval component of the current active {@link Location} update request.
private android.location.Location
mLastLocationUpdate
The {@link Location} most recently received via {@link #onLocationChanged(Location)}.
private boolean
mPendingUpdate
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()}
Constructors Summary
public GeofenceManager(android.content.Context context, LocationBlacklist blacklist)


         
        mContext = context;
        mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
        mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mHandler = new GeofenceHandler();
        mBlacklist = blacklist;
    
Methods Summary
public voidaddFence(android.location.LocationRequest request, android.location.Geofence geofence, android.app.PendingIntent intent, int allowedResolutionLevel, int uid, java.lang.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(), allowedResolutionLevel, uid, 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);
            scheduleUpdateFencesLocked();
        }
    
public voiddump(java.io.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");
        }
    
private android.location.LocationgetFreshLocationLocked()
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

        // 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;
    
public voidonLocationChanged(android.location.Location location)

        synchronized (mLock) {
            if (mReceivingLocationUpdates) {
                mLastLocationUpdate = location;
            }

            // Update the fences immediately before returning in
            // case the caller is holding a wakelock.
            if (mPendingUpdate) {
                mHandler.removeMessages(MSG_UPDATE_FENCES);
            } else {
                mPendingUpdate = true;
            }
        }
        updateFences();
    
public voidonProviderDisabled(java.lang.String provider)

 
public voidonProviderEnabled(java.lang.String provider)

 
public voidonSendFinished(android.app.PendingIntent pendingIntent, android.content.Intent intent, int resultCode, java.lang.String resultData, android.os.Bundle resultExtras)

        mWakeLock.release();
    
public voidonStatusChanged(java.lang.String provider, int status, android.os.Bundle extras)

 
private voidremoveExpiredFencesLocked()

        long time = SystemClock.elapsedRealtime();
        Iterator<GeofenceState> iter = mFences.iterator();
        while (iter.hasNext()) {
            GeofenceState state = iter.next();
            if (state.mExpireAt < time) {
                iter.remove();
            }
        }
    
public voidremoveFence(android.location.Geofence fence, android.app.PendingIntent intent)

        if (D) {
            Slog.d(TAG, "removeFence: fence=" + fence + ", intent=" + intent);
        }

        synchronized (mLock) {
            Iterator<GeofenceState> iter = mFences.iterator();
            while (iter.hasNext()) {
                GeofenceState state = iter.next();
                if (state.mIntent.equals(intent)) {

                    if (fence == null) {
                        // always remove
                        iter.remove();
                    } else {
                        // just remove matching fences
                        if (fence.equals(state.mFence)) {
                            iter.remove();
                        }
                    }
                }
            }
            scheduleUpdateFencesLocked();
        }
    
public voidremoveFence(java.lang.String packageName)

        if (D) {
            Slog.d(TAG, "removeFence: packageName=" + packageName);
        }

        synchronized (mLock) {
            Iterator<GeofenceState> iter = mFences.iterator();
            while (iter.hasNext()) {
                GeofenceState state = iter.next();
                if (state.mPackageName.equals(packageName)) {
                    iter.remove();
                }
            }
            scheduleUpdateFencesLocked();
        }
    
private voidscheduleUpdateFencesLocked()

        if (!mPendingUpdate) {
            mPendingUpdate = true;
            mHandler.sendEmptyMessage(MSG_UPDATE_FENCES);
        }
    
private voidsendIntent(android.app.PendingIntent pendingIntent, android.content.Intent intent)

        mWakeLock.acquire();
        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 voidsendIntentEnter(android.app.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 voidsendIntentExit(android.app.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 voidupdateFences()
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.

        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) {
                        Slog.d(TAG, "skipping geofence processing for blacklisted app: "
                                + state.mPackageName);
                    }
                    continue;
                }

                int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
                if (op >= 0) {
                    if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
                            state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
                        if (D) {
                            Slog.d(TAG, "skipping geofence processing for no op app: "
                                    + state.mPackageName);
                        }
                        continue;
                    }
                }

                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 (!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);
            }
        }

        // release lock before sending intents
        for (PendingIntent intent : exitIntents) {
            sendIntentExit(intent);
        }
        for (PendingIntent intent : enterIntents) {
            sendIntentEnter(intent);
        }