1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.location; 18 19 import java.io.PrintWriter; 20 import java.util.Iterator; 21 import java.util.LinkedList; 22 import java.util.List; 23 24 import android.app.PendingIntent; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.location.Geofence; 28 import android.location.Location; 29 import android.location.LocationListener; 30 import android.location.LocationManager; 31 import android.location.LocationRequest; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Message; 35 import android.os.PowerManager; 36 import android.os.SystemClock; 37 import android.util.Slog; 38 39 import com.android.server.LocationManagerService; 40 41 public class GeofenceManager implements LocationListener, PendingIntent.OnFinished { 42 private static final String TAG = "GeofenceManager"; 43 private static final boolean D = LocationManagerService.D; 44 45 private static final int MSG_UPDATE_FENCES = 1; 46 47 /** 48 * Assume a maximum land speed, as a heuristic to throttle location updates. 49 * (Air travel should result in an airplane mode toggle which will 50 * force a new location update anyway). 51 */ 52 private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) 53 54 /** 55 * Maximum age after which a location is no longer considered fresh enough to use. 56 */ 57 private static final long MAX_AGE_NANOS = 5 * 60 * 1000000000L; // five minutes 58 59 /** 60 * Most frequent update interval allowed. 61 */ 62 private static final long MIN_INTERVAL_MS = 1 * 60 * 1000; // one minute 63 64 /** 65 * Least frequent update interval allowed. 66 */ 67 private static final long MAX_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours 68 69 private final Context mContext; 70 private final LocationManager mLocationManager; 71 private final PowerManager.WakeLock mWakeLock; 72 private final GeofenceHandler mHandler; 73 private final LocationBlacklist mBlacklist; 74 75 private Object mLock = new Object(); 76 77 // access to members below is synchronized on mLock 78 /** 79 * A list containing all registered geofences. 80 */ 81 private List<GeofenceState> mFences = new LinkedList<GeofenceState>(); 82 83 /** 84 * This is set true when we have an active request for {@link Location} updates via 85 * {@link LocationManager#requestLocationUpdates(LocationRequest, LocationListener, 86 * android.os.Looper). 87 */ 88 private boolean mReceivingLocationUpdates; 89 90 /** 91 * The update interval component of the current active {@link Location} update request. 92 */ 93 private long mLocationUpdateInterval; 94 95 /** 96 * The {@link Location} most recently received via {@link #onLocationChanged(Location)}. 97 */ 98 private Location mLastLocationUpdate; 99 100 /** 101 * This is set true when a {@link Location} is received via 102 * {@link #onLocationChanged(Location)} or {@link #scheduleUpdateFencesLocked()}, and cleared 103 * when that Location has been processed via {@link #updateFences()} 104 */ 105 private boolean mPendingUpdate; 106 107 public GeofenceManager(Context context, LocationBlacklist blacklist) { 108 mContext = context; 109 mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); 110 PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 111 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 112 mHandler = new GeofenceHandler(); 113 mBlacklist = blacklist; 114 } 115 116 public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, 117 int uid, String packageName) { 118 if (D) { 119 Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence 120 + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName); 121 } 122 123 GeofenceState state = new GeofenceState(geofence, 124 request.getExpireAt(), packageName, intent); 125 synchronized (mLock) { 126 // first make sure it doesn't already exist 127 for (int i = mFences.size() - 1; i >= 0; i--) { 128 GeofenceState w = mFences.get(i); 129 if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) { 130 // already exists, remove the old one 131 mFences.remove(i); 132 break; 133 } 134 } 135 mFences.add(state); 136 scheduleUpdateFencesLocked(); 137 } 138 } 139 140 public void removeFence(Geofence fence, PendingIntent intent) { 141 if (D) { 142 Slog.d(TAG, "removeFence: fence=" + fence + ", intent=" + intent); 143 } 144 145 synchronized (mLock) { 146 Iterator<GeofenceState> iter = mFences.iterator(); 147 while (iter.hasNext()) { 148 GeofenceState state = iter.next(); 149 if (state.mIntent.equals(intent)) { 150 151 if (fence == null) { 152 // always remove 153 iter.remove(); 154 } else { 155 // just remove matching fences 156 if (fence.equals(state.mFence)) { 157 iter.remove(); 158 } 159 } 160 } 161 } 162 scheduleUpdateFencesLocked(); 163 } 164 } 165 166 public void removeFence(String packageName) { 167 if (D) { 168 Slog.d(TAG, "removeFence: packageName=" + packageName); 169 } 170 171 synchronized (mLock) { 172 Iterator<GeofenceState> iter = mFences.iterator(); 173 while (iter.hasNext()) { 174 GeofenceState state = iter.next(); 175 if (state.mPackageName.equals(packageName)) { 176 iter.remove(); 177 } 178 } 179 scheduleUpdateFencesLocked(); 180 } 181 } 182 183 private void removeExpiredFencesLocked() { 184 long time = SystemClock.elapsedRealtime(); 185 Iterator<GeofenceState> iter = mFences.iterator(); 186 while (iter.hasNext()) { 187 GeofenceState state = iter.next(); 188 if (state.mExpireAt < time) { 189 iter.remove(); 190 } 191 } 192 } 193 194 private void scheduleUpdateFencesLocked() { 195 if (!mPendingUpdate) { 196 mPendingUpdate = true; 197 mHandler.sendEmptyMessage(MSG_UPDATE_FENCES); 198 } 199 } 200 201 /** 202 * Returns the location received most recently from {@link #onLocationChanged(Location)}, 203 * or consult {@link LocationManager#getLastLocation()} if none has arrived. Does not return 204 * either if the location would be too stale to be useful. 205 * 206 * @return a fresh, valid Location, or null if none is available 207 */ 208 private Location getFreshLocationLocked() { 209 // Prefer mLastLocationUpdate to LocationManager.getLastLocation(). 210 Location location = mReceivingLocationUpdates ? mLastLocationUpdate : null; 211 if (location == null && !mFences.isEmpty()) { 212 location = mLocationManager.getLastLocation(); 213 } 214 215 // Early out for null location. 216 if (location == null) { 217 return null; 218 } 219 220 // Early out for stale location. 221 long now = SystemClock.elapsedRealtimeNanos(); 222 if (now - location.getElapsedRealtimeNanos() > MAX_AGE_NANOS) { 223 return null; 224 } 225 226 // Made it this far? Return our fresh, valid location. 227 return location; 228 } 229 230 /** 231 * The geofence update loop. This function removes expired fences, then tests the most 232 * recently-received {@link Location} against each registered {@link GeofenceState}, sending 233 * {@link Intent}s for geofences that have been tripped. It also adjusts the active location 234 * update request with {@link LocationManager} as appropriate for any active geofences. 235 */ 236 // Runs on the handler. 237 private void updateFences() { 238 List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); 239 List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); 240 241 synchronized (mLock) { 242 mPendingUpdate = false; 243 244 // Remove expired fences. 245 removeExpiredFencesLocked(); 246 247 // Get a location to work with, either received via onLocationChanged() or 248 // via LocationManager.getLastLocation(). 249 Location location = getFreshLocationLocked(); 250 251 // Update all fences. 252 // Keep track of the distance to the nearest fence. 253 double minFenceDistance = Double.MAX_VALUE; 254 boolean needUpdates = false; 255 for (GeofenceState state : mFences) { 256 if (mBlacklist.isBlacklisted(state.mPackageName)) { 257 if (D) { 258 Slog.d(TAG, "skipping geofence processing for blacklisted app: " 259 + state.mPackageName); 260 } 261 continue; 262 } 263 264 needUpdates = true; 265 if (location != null) { 266 int event = state.processLocation(location); 267 if ((event & GeofenceState.FLAG_ENTER) != 0) { 268 enterIntents.add(state.mIntent); 269 } 270 if ((event & GeofenceState.FLAG_EXIT) != 0) { 271 exitIntents.add(state.mIntent); 272 } 273 274 // FIXME: Ideally this code should take into account the accuracy of the 275 // location fix that was used to calculate the distance in the first place. 276 double fenceDistance = state.getDistanceToBoundary(); // MAX_VALUE if unknown 277 if (fenceDistance < minFenceDistance) { 278 minFenceDistance = fenceDistance; 279 } 280 } 281 } 282 283 // Request or cancel location updates if needed. 284 if (needUpdates) { 285 // Request location updates. 286 // Compute a location update interval based on the distance to the nearest fence. 287 long intervalMs; 288 if (location != null && Double.compare(minFenceDistance, Double.MAX_VALUE) != 0) { 289 intervalMs = (long)Math.min(MAX_INTERVAL_MS, Math.max(MIN_INTERVAL_MS, 290 minFenceDistance * 1000 / MAX_SPEED_M_S)); 291 } else { 292 intervalMs = MIN_INTERVAL_MS; 293 } 294 if (!mReceivingLocationUpdates || mLocationUpdateInterval != intervalMs) { 295 mReceivingLocationUpdates = true; 296 mLocationUpdateInterval = intervalMs; 297 mLastLocationUpdate = location; 298 299 LocationRequest request = new LocationRequest(); 300 request.setInterval(intervalMs).setFastestInterval(0); 301 mLocationManager.requestLocationUpdates(request, this, mHandler.getLooper()); 302 } 303 } else { 304 // Cancel location updates. 305 if (mReceivingLocationUpdates) { 306 mReceivingLocationUpdates = false; 307 mLocationUpdateInterval = 0; 308 mLastLocationUpdate = null; 309 310 mLocationManager.removeUpdates(this); 311 } 312 } 313 314 if (D) { 315 Slog.d(TAG, "updateFences: location=" + location 316 + ", mFences.size()=" + mFences.size() 317 + ", mReceivingLocationUpdates=" + mReceivingLocationUpdates 318 + ", mLocationUpdateInterval=" + mLocationUpdateInterval 319 + ", mLastLocationUpdate=" + mLastLocationUpdate); 320 } 321 } 322 323 // release lock before sending intents 324 for (PendingIntent intent : exitIntents) { 325 sendIntentExit(intent); 326 } 327 for (PendingIntent intent : enterIntents) { 328 sendIntentEnter(intent); 329 } 330 } 331 332 private void sendIntentEnter(PendingIntent pendingIntent) { 333 if (D) { 334 Slog.d(TAG, "sendIntentEnter: pendingIntent=" + pendingIntent); 335 } 336 337 Intent intent = new Intent(); 338 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); 339 sendIntent(pendingIntent, intent); 340 } 341 342 private void sendIntentExit(PendingIntent pendingIntent) { 343 if (D) { 344 Slog.d(TAG, "sendIntentExit: pendingIntent=" + pendingIntent); 345 } 346 347 Intent intent = new Intent(); 348 intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); 349 sendIntent(pendingIntent, intent); 350 } 351 352 private void sendIntent(PendingIntent pendingIntent, Intent intent) { 353 mWakeLock.acquire(); 354 try { 355 pendingIntent.send(mContext, 0, intent, this, null, 356 android.Manifest.permission.ACCESS_FINE_LOCATION); 357 } catch (PendingIntent.CanceledException e) { 358 removeFence(null, pendingIntent); 359 mWakeLock.release(); 360 } 361 // ...otherwise, mWakeLock.release() gets called by onSendFinished() 362 } 363 364 // Runs on the handler (which was passed into LocationManager.requestLocationUpdates()) 365 @Override 366 public void onLocationChanged(Location location) { 367 synchronized (mLock) { 368 if (mReceivingLocationUpdates) { 369 mLastLocationUpdate = location; 370 } 371 372 // Update the fences immediately before returning in 373 // case the caller is holding a wakelock. 374 if (mPendingUpdate) { 375 mHandler.removeMessages(MSG_UPDATE_FENCES); 376 } else { 377 mPendingUpdate = true; 378 } 379 } 380 updateFences(); 381 } 382 383 @Override 384 public void onStatusChanged(String provider, int status, Bundle extras) { } 385 386 @Override 387 public void onProviderEnabled(String provider) { } 388 389 @Override 390 public void onProviderDisabled(String provider) { } 391 392 @Override 393 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, 394 String resultData, Bundle resultExtras) { 395 mWakeLock.release(); 396 } 397 398 public void dump(PrintWriter pw) { 399 pw.println(" Geofences:"); 400 401 for (GeofenceState state : mFences) { 402 pw.append(" "); 403 pw.append(state.mPackageName); 404 pw.append(" "); 405 pw.append(state.mFence.toString()); 406 pw.append("\n"); 407 } 408 } 409 410 private final class GeofenceHandler extends Handler { 411 public GeofenceHandler() { 412 super(true /*async*/); 413 } 414 415 @Override 416 public void handleMessage(Message msg) { 417 switch (msg.what) { 418 case MSG_UPDATE_FENCES: { 419 updateFences(); 420 break; 421 } 422 } 423 } 424 } 425 } 426