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